diff options
Diffstat (limited to 'lib')
565 files changed, 28587 insertions, 18053 deletions
diff --git a/lib/asn1/c_src/asn1_erl_nif.c b/lib/asn1/c_src/asn1_erl_nif.c index 797be6d4f8..da43af3405 100644 --- a/lib/asn1/c_src/asn1_erl_nif.c +++ b/lib/asn1/c_src/asn1_erl_nif.c @@ -999,7 +999,7 @@ static int ber_decode_value(ErlNifEnv* env, ERL_NIF_TERM *value, unsigned char * while (*ib_index < end_index) { if ((maybe_ret = ber_decode(env, &term, in_buf, ib_index, - *ib_index + len)) <= ASN1_ERROR + end_index )) <= ASN1_ERROR ) return maybe_ret; curr_head = enif_make_list_cell(env, term, curr_head); diff --git a/lib/asn1/doc/src/asn1ct.xml b/lib/asn1/doc/src/asn1ct.xml index ccf07a9cc1..e86dbd9f5e 100644 --- a/lib/asn1/doc/src/asn1ct.xml +++ b/lib/asn1/doc/src/asn1ct.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>asn1.sgml</file> </header> - <module>asn1ct</module> + <module since="">asn1ct</module> <modulesummary>ASN.1 compiler and compile-time support functions</modulesummary> <description> <p>The ASN.1 compiler takes an ASN.1 module as input and generates a @@ -72,8 +72,8 @@ <funcs> <func> - <name>compile(Asn1module) -> ok | {error, Reason}</name> - <name>compile(Asn1module, Options) -> ok | {error, Reason}</name> + <name since="">compile(Asn1module) -> ok | {error, Reason}</name> + <name since="">compile(Asn1module, Options) -> ok | {error, Reason}</name> <fsummary>Compiles an ASN.1 module and generates encode/decode functions according to encoding rules BER or PER.</fsummary> <type> <v>Asn1module = atom() | string()</v> @@ -336,7 +336,7 @@ File3.asn</pre> </func> <func> - <name>value(Module, Type) -> {ok, Value} | {error, Reason}</name> + <name since="">value(Module, Type) -> {ok, Value} | {error, Reason}</name> <fsummary>Creates an ASN.1 value for test purposes.</fsummary> <type> <v>Module = Type = atom()</v> @@ -361,9 +361,9 @@ File3.asn</pre> </func> <func> - <name>test(Module) -> ok | {error, Reason}</name> - <name>test(Module, Type | Options) -> ok | {error, Reason}</name> - <name>test(Module, Type, Value | Options) -> ok | {error, Reason}</name> + <name since="">test(Module) -> ok | {error, Reason}</name> + <name since="">test(Module, Type | Options) -> ok | {error, Reason}</name> + <name since="">test(Module, Type, Value | Options) -> ok | {error, Reason}</name> <fsummary>Performs a test of encode and decode for types in an ASN.1 module.</fsummary> <type> <v>Module = Type = atom()</v> diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml index 086b5f38c6..22ca7840de 100644 --- a/lib/asn1/doc/src/notes.xml +++ b/lib/asn1/doc/src/notes.xml @@ -32,6 +32,22 @@ <p>This document describes the changes made to the asn1 application.</p> +<section><title>Asn1 5.0.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handle erroneous length during decode (BER only) without + crashing.</p> + <p> + Own Id: OTP-15470 Aux Id: ERIERL-278 </p> + </item> + </list> + </section> + +</section> + <section><title>Asn1 5.0.7</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -69,6 +85,22 @@ </section> +<section><title>Asn1 5.0.5.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handle erroneous length during decode (BER only) without + crashing.</p> + <p> + Own Id: OTP-15470 Aux Id: ERIERL-278 </p> + </item> + </list> + </section> + +</section> + <section><title>Asn1 5.0.5.1</title> <section><title>Known Bugs and Problems</title> @@ -1980,4 +2012,3 @@ <!-- p>There are also release notes for <url href="notes_history.html">older versions</url>.</p --> </section> </chapter> - diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl index 5506923341..ab78678110 100644 --- a/lib/asn1/test/asn1_SUITE.erl +++ b/lib/asn1/test/asn1_SUITE.erl @@ -63,7 +63,8 @@ groups() -> constraint_equivalence]}, {ber, Parallel, - [ber_choiceinseq, + [ber_decode_invalid_length, + ber_choiceinseq, % Uses 'SOpttest' ber_optional, tagdefault_automatic]}, @@ -665,6 +666,19 @@ module_test(M0, Config, Rule, Opts) -> end end. +ber_decode_invalid_length(_Config) -> + Bin = <<48,129,157,48,0,2,1,2,164,0,48,129,154,49,24,48,22,6, + 3,85,4,10,19,15,69,120,97,109,112,108,101,32,67,111, + 109,112,97,110,121,49,29,48,27,6,9,42,134,72,134,247, + 13,1,9,1,22,14,99,97,64,101,120,97,109,112,108,101,46, + 99,111,109,49,13,48,11,6,3,85,4,7,19,4,79,117,108,117, + 49,26,48,24,6,3,85,4,8,19,17,80,111,104,106,111,105, + 115,45,80,111,104,106,97,110,109,97,97,49,11,48,9,6,3, + 85,4,6,19,2,70,73,49,19,48,17,6,3,85,4,3,19,10,69,120, + 97,109,112,108,101,32,67,65,49,11,48,16,6,3,85,4,11, + 19,9,84,101>>, + {'EXIT',{error,{asn1,{invalid_value,12}}}} = (catch asn1rt_nif:decode_ber_tlv(Bin)), + ok. ber_choiceinseq(Config) -> test(Config, fun ber_choiceinseq/3, [ber]). diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk index 970479cc44..69f1af28e8 100644 --- a/lib/asn1/vsn.mk +++ b/lib/asn1/vsn.mk @@ -1 +1 @@ -ASN1_VSN = 5.0.7 +ASN1_VSN = 5.0.8 diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml index a3b3f927eb..7887a2c3ea 100644 --- a/lib/common_test/doc/src/common_test_app.xml +++ b/lib/common_test/doc/src/common_test_app.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>common_test_app.sgml</file> </header> - <module>common_test</module> + <module since="">common_test</module> <modulesummary>A framework for automated testing of any target nodes.</modulesummary> <description> @@ -68,7 +68,7 @@ <funcs> <func> - <name>Module:all() -> Tests | {skip,Reason} </name> + <name since="">Module:all() -> Tests | {skip,Reason} </name> <fsummary>Returns the list of all test case groups and test cases in the module.</fsummary> <type> @@ -115,7 +115,7 @@ </func> <func> - <name>Module:groups() -> GroupDefs</name> + <name since="">Module:groups() -> GroupDefs</name> <fsummary>Returns a list of test case group definitions.</fsummary> <type> <v>GroupDefs = [Group]</v> @@ -140,7 +140,7 @@ </func> <func> - <name>Module:suite() -> [Info] </name> + <name since="">Module:suite() -> [Info] </name> <fsummary>Test suite info function (providing default data for the suite).</fsummary> <type> @@ -213,7 +213,7 @@ </func> <func> - <name>Module:init_per_suite(Config) -> NewConfig | {skip,Reason} | + <name since="">Module:init_per_suite(Config) -> NewConfig | {skip,Reason} | {skip_and_save,Reason,SaveConfig}</name> <fsummary>Test suite initializations.</fsummary> <type> @@ -248,7 +248,7 @@ </func> <func> - <name>Module:end_per_suite(Config) -> term() | + <name since="">Module:end_per_suite(Config) -> term() | {save_config,SaveConfig}</name> <fsummary>Test suite finalization.</fsummary> <type> @@ -272,7 +272,7 @@ </func> <func> - <name>Module:group(GroupName) -> [Info] </name> + <name since="OTP R15B">Module:group(GroupName) -> [Info] </name> <fsummary>Test case group information function (providing default data for a test case group, that is, its test cases and subgroups).</fsummary> @@ -352,7 +352,7 @@ </func> <func> - <name>Module:init_per_group(GroupName, Config) -> NewConfig | + <name since="">Module:init_per_group(GroupName, Config) -> NewConfig | {skip,Reason}</name> <fsummary>Test case group initializations.</fsummary> <type> @@ -390,7 +390,7 @@ </func> <func> - <name>Module:end_per_group(GroupName, Config) -> term() | + <name since="">Module:end_per_group(GroupName, Config) -> term() | {return_group_result,Status}</name> <fsummary>Test case group finalization.</fsummary> <type> @@ -424,7 +424,7 @@ </func> <func> - <name>Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name> + <name since="">Module:init_per_testcase(TestCase, Config) -> NewConfig | {fail,Reason} | {skip,Reason}</name> <fsummary>Test case initializations.</fsummary> <type> <v> TestCase = atom()</v> @@ -454,7 +454,7 @@ </func> <func> - <name>Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name> + <name since="">Module:end_per_testcase(TestCase, Config) -> term() | {fail,Reason} | {save_config,SaveConfig}</name> <fsummary>Test case finalization.</fsummary> <type> <v>TestCase = atom()</v> @@ -486,7 +486,7 @@ </func> <func> - <name>Module:Testcase() -> [Info] </name> + <name since="OTP R14B">Module:Testcase() -> [Info] </name> <fsummary>Test case information function.</fsummary> <type> <v>Info = {timetrap,Time} | {require,Required} | {require,Name,Required} | {userdata,UserData} | {silent_connections,Conns}</v> @@ -560,7 +560,7 @@ </func> <func> - <name>Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name> + <name since="OTP R14B">Module:Testcase(Config) -> term() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name> <fsummary>A test case.</fsummary> <type> <v>Config = SaveConfig = [{Key,Value}]</v> diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml index c0380c4142..83c0ecb309 100644 --- a/lib/common_test/doc/src/ct.xml +++ b/lib/common_test/doc/src/ct.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct.xml</file> </header> - <module>ct</module> + <module since="">ct</module> <modulesummary>Main user interface for the Common Test framework.</modulesummary> <description> @@ -139,7 +139,7 @@ <funcs> <func> - <name>abort_current_testcase(Reason) -> ok | {error, ErrorReason}</name> + <name since="">abort_current_testcase(Reason) -> ok | {error, ErrorReason}</name> <fsummary>Aborts the currently executing test case.</fsummary> <type> <v>Reason = term()</v> @@ -157,7 +157,7 @@ </func> <func> - <name>add_config(Callback, Config) -> ok | {error, Reason}</name> + <name since="OTP R14B">add_config(Callback, Config) -> ok | {error, Reason}</name> <fsummary>Loads configuration variables using the specified callback module and configuration string.</fsummary> <type> @@ -176,7 +176,7 @@ </func> <func> - <name>break(Comment) -> ok | {error, Reason}</name> + <name since="OTP R15B02">break(Comment) -> ok | {error, Reason}</name> <fsummary>Cancels any active timetrap and pause the execution of the current test case until the user calls function continue/0.</fsummary> <type> @@ -206,7 +206,7 @@ </func> <func> - <name>break(TestCase, Comment) -> ok | {error, Reason}</name> + <name since="OTP R15B02">break(TestCase, Comment) -> ok | {error, Reason}</name> <fsummary>Works the same way as break/1, only argument TestCase makes it possible to pause a test case executing in a parallel group.</fsummary> <type> @@ -228,7 +228,7 @@ </func> <func> - <name>capture_get() -> ListOfStrings</name> + <name since="OTP R15B">capture_get() -> ListOfStrings</name> <fsummary>Equivalent to capture_get([default]).</fsummary> <type> <v>ListOfStrings = [string()]</v> @@ -240,7 +240,7 @@ </func> <func> - <name>capture_get(ExclCategories) -> ListOfStrings</name> + <name since="OTP R15B">capture_get(ExclCategories) -> ListOfStrings</name> <fsummary>Returns and purges the list of text strings buffered during the latest session of capturing printouts to stdout.</fsummary> <type> @@ -262,7 +262,7 @@ </func> <func> - <name>capture_start() -> ok</name> + <name since="OTP R15B">capture_start() -> ok</name> <fsummary>Starts capturing all text strings printed to stdout during execution of the test case.</fsummary> <desc><marker id="capture_start-0"/> @@ -276,7 +276,7 @@ </func> <func> - <name>capture_stop() -> ok</name> + <name since="OTP R15B">capture_stop() -> ok</name> <fsummary>Stops capturing text strings (a session started with capture_start/0).</fsummary> <desc><marker id="capture_stop-0"/> @@ -290,7 +290,7 @@ </func> <func> - <name>comment(Comment) -> ok</name> + <name since="">comment(Comment) -> ok</name> <fsummary>Prints the specified Comment in the comment field in the table on the test suite result page.</fsummary> <type> @@ -307,7 +307,7 @@ </func> <func> - <name>comment(Format, Args) -> ok</name> + <name since="OTP R15B">comment(Format, Args) -> ok</name> <fsummary>Prints the formatted string in the comment field in the table on the test suite result page.</fsummary> <type> @@ -326,7 +326,7 @@ </func> <func> - <name>continue() -> ok</name> + <name since="OTP R15B02">continue() -> ok</name> <fsummary>This function must be called to continue after a test case (not executing in a parallel group) has called break/1.</fsummary> <desc><marker id="continue-0"/> @@ -337,7 +337,7 @@ </func> <func> - <name>continue(TestCase) -> ok</name> + <name since="OTP R15B02">continue(TestCase) -> ok</name> <fsummary>This function must be called to continue after a test case has called break/2.</fsummary> <type> @@ -353,7 +353,7 @@ </func> <func> - <name>decrypt_config_file(EncryptFileName, TargetFileName) -> ok | {error, Reason}</name> + <name since="">decrypt_config_file(EncryptFileName, TargetFileName) -> ok | {error, Reason}</name> <fsummary>Decrypts EncryptFileName, previously generated with encrypt_config_file/2,3.</fsummary> <type> @@ -372,7 +372,7 @@ </func> <func> - <name>decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> ok | {error, Reason}</name> + <name since="">decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) -> ok | {error, Reason}</name> <fsummary>Decrypts EncryptFileName, previously generated with encrypt_config_file/2,3.</fsummary> <type> @@ -390,7 +390,7 @@ </func> <func> - <name>encrypt_config_file(SrcFileName, EncryptFileName) -> ok | {error, Reason}</name> + <name since="">encrypt_config_file(SrcFileName, EncryptFileName) -> ok | {error, Reason}</name> <fsummary>Encrypts the source configuration file with DES3 and saves the result in file EncryptFileName.</fsummary> <type> @@ -416,7 +416,7 @@ </func> <func> - <name>encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> ok | {error, Reason}</name> + <name since="">encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) -> ok | {error, Reason}</name> <fsummary>Encrypts the source configuration file with DES3 and saves the result in the target file EncryptFileName.</fsummary> <type> @@ -442,7 +442,7 @@ </func> <func> - <name>fail(Reason) -> ok</name> + <name since="">fail(Reason) -> ok</name> <fsummary>Terminates a test case with the specified error Reason.</fsummary> <type> @@ -454,7 +454,7 @@ </func> <func> - <name>fail(Format, Args) -> ok</name> + <name since="OTP R15B">fail(Format, Args) -> ok</name> <fsummary>Terminates a test case with an error message specified by a format string and a list of values (used as arguments to io_lib:format/2).</fsummary> @@ -470,7 +470,7 @@ </func> <func> - <name>get_config(Required) -> Value</name> + <name since="">get_config(Required) -> Value</name> <fsummary>Equivalent to get_config(Required, undefined, []).</fsummary> <desc><marker id="get_config-1"/> <p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required, @@ -479,7 +479,7 @@ </func> <func> - <name>get_config(Required, Default) -> Value</name> + <name since="">get_config(Required, Default) -> Value</name> <fsummary>Equivalent to get_config(Required, Default, []).</fsummary> <desc><marker id="get_config-2"/> <p>Equivalent to <seealso marker="#get_config-3"><c>ct:get_config(Required, @@ -488,7 +488,7 @@ </func> <func> - <name>get_config(Required, Default, Opts) -> ValueOrElement</name> + <name since="">get_config(Required, Default, Opts) -> ValueOrElement</name> <fsummary>Reads configuration data values.</fsummary> <type> <v>Required = KeyOrName | {KeyOrName, SubKey} | {KeyOrName, SubKey, SubKey}</v> @@ -554,7 +554,7 @@ </func> <func> - <name>get_event_mgr_ref() -> EvMgrRef</name> + <name since="OTP 17.5">get_event_mgr_ref() -> EvMgrRef</name> <fsummary>Gets a reference to the <c>Common Test</c> event manager.</fsummary> <type> <v>EvMgrRef = atom()</v> @@ -572,7 +572,7 @@ </func> <func> - <name>get_progname() -> string()</name> + <name since="OTP 21.0">get_progname() -> string()</name> <fsummary>Returns the command used to start this Erlang instance.</fsummary> <desc><marker id="get_progname-0"/> <p>Returns the command used to start this Erlang instance. @@ -582,7 +582,7 @@ </func> <func> - <name>get_status() -> TestStatus | {error, Reason} | no_tests_running</name> + <name since="">get_status() -> TestStatus | {error, Reason} | no_tests_running</name> <fsummary>Returns status of ongoing test.</fsummary> <type> <v>TestStatus = [StatusElem]</v> @@ -608,7 +608,7 @@ </func> <func> - <name>get_target_name(Handle) -> {ok, TargetName} | {error, Reason}</name> + <name since="">get_target_name(Handle) -> {ok, TargetName} | {error, Reason}</name> <fsummary>Returns the name of the target that the specified connection belongs to.</fsummary> <type> @@ -622,7 +622,7 @@ </func> <func> - <name>get_testspec_terms() -> TestSpecTerms | undefined</name> + <name since="OTP 18.0">get_testspec_terms() -> TestSpecTerms | undefined</name> <fsummary>Gets a list of all test specification terms used to configure and run this test.</fsummary> <type> @@ -636,7 +636,7 @@ </func> <func> - <name>get_testspec_terms(Tags) -> TestSpecTerms | undefined</name> + <name since="OTP 18.0">get_testspec_terms(Tags) -> TestSpecTerms | undefined</name> <fsummary>Reads one or more terms from the test specification used to configure and run this test.</fsummary> <type> @@ -663,7 +663,7 @@ </func> <func> - <name>get_timetrap_info() -> {Time, {Scaling,ScaleVal}}</name> + <name since="OTP R15B">get_timetrap_info() -> {Time, {Scaling,ScaleVal}}</name> <fsummary>Reads information about the timetrap set for the current test case.</fsummary> <type> @@ -682,7 +682,7 @@ </func> <func> - <name>get_verbosity(Category) -> Level | undefined</name> + <name since="OTP 19.1">get_verbosity(Category) -> Level | undefined</name> <fsummary>Read the verbosity level for a logging category.</fsummary> <type> <v>Category = default | atom()</v> @@ -697,7 +697,7 @@ </func> <func> - <name>install(Opts) -> ok | {error, Reason}</name> + <name since="">install(Opts) -> ok | {error, Reason}</name> <fsummary>Installs configuration files and event handlers.</fsummary> <type> <v>Opts = [Opt]</v> @@ -724,7 +724,7 @@ </func> <func> - <name>listenv(Telnet) -> [Env]</name> + <name since="">listenv(Telnet) -> [Env]</name> <fsummary>Performs command listenv on the specified Telnet connection and returns the result as a list of key-value pairs.</fsummary> <type> @@ -740,7 +740,7 @@ </func> <func> - <name>log(Format) -> ok</name> + <name since="">log(Format) -> ok</name> <fsummary>Equivalent to log(default, 50, Format, [], []).</fsummary> <desc><marker id="log-1"/> <p>Equivalent to @@ -749,7 +749,7 @@ </func> <func> - <name>log(X1, X2) -> ok</name> + <name since="">log(X1, X2) -> ok</name> <fsummary>Equivalent to log(Category, Importance, Format, FormatArgs, []).</fsummary> <type> @@ -763,7 +763,7 @@ </func> <func> - <name>log(X1, X2, X3) -> ok</name> + <name since="">log(X1, X2, X3) -> ok</name> <fsummary>Equivalent to log(Category, Importance, Format, FormatArgs, Opts).</fsummary> <type> @@ -778,7 +778,7 @@ </func> <func> - <name>log(X1, X2, X3, X4) -> ok</name> + <name since="OTP R15B02">log(X1, X2, X3, X4) -> ok</name> <fsummary>Equivalent to log(Category, Importance, Format, FormatArgs, Opts).</fsummary> <type> @@ -794,7 +794,7 @@ </func> <func> - <name>log(Category, Importance, Format, FormatArgs, Opts) -> ok</name> + <name since="OTP 18.3">log(Category, Importance, Format, FormatArgs, Opts) -> ok</name> <fsummary>Prints from a test case to the log file.</fsummary> <type> <v>Category = atom()</v> @@ -825,7 +825,7 @@ </func> <func> - <name>make_priv_dir() -> ok | {error, Reason}</name> + <name since="OTP R15B01">make_priv_dir() -> ok | {error, Reason}</name> <fsummary>If the test has been started with option create_priv_dir set to manual_per_tc, in order for the test case to use the private directory, it must first create it by calling this function.</fsummary> @@ -841,7 +841,7 @@ </func> <func> - <name>notify(Name, Data) -> ok</name> + <name since="OTP R15B02">notify(Name, Data) -> ok</name> <fsummary>Sends an asynchronous notification of type Name with Data to the <c>Common Test</c> event manager.</fsummary> <type> @@ -859,7 +859,7 @@ </func> <func> - <name>pal(Format) -> ok</name> + <name since="">pal(Format) -> ok</name> <fsummary>Equivalent to pal(default, 50, Format, [], []).</fsummary> <desc><marker id="pal-1"/> <p>Equivalent to @@ -869,7 +869,7 @@ </func> <func> - <name>pal(X1, X2) -> ok</name> + <name since="">pal(X1, X2) -> ok</name> <fsummary>Equivalent to pal(Category, Importance, Format, FormatArgs, []).</fsummary> <type> @@ -883,7 +883,7 @@ </func> <func> - <name>pal(X1, X2, X3) -> ok</name> + <name since="">pal(X1, X2, X3) -> ok</name> <fsummary>Equivalent to pal(Category, Importance, Format, FormatArgs, Opts).</fsummary> <type> @@ -898,7 +898,7 @@ </func> <func> - <name>pal(X1, X2, X3, X4) -> ok</name> + <name since="OTP R15B02">pal(X1, X2, X3, X4) -> ok</name> <fsummary>Equivalent to pal(Category, Importance, Format, FormatArgs, Opts).</fsummary> <type> @@ -914,7 +914,7 @@ </func> <func> - <name>pal(Category, Importance, Format, FormatArgs, Opts) -> ok</name> + <name since="OTP 19.2">pal(Category, Importance, Format, FormatArgs, Opts) -> ok</name> <fsummary>Prints and logs from a test case.</fsummary> <type> <v>Category = atom()</v> @@ -945,7 +945,7 @@ </func> <func> - <name>parse_table(Data) -> {Heading, Table}</name> + <name since="">parse_table(Data) -> {Heading, Table}</name> <fsummary>Parses the printout from an SQL table and returns a list of tuples.</fsummary> <type> @@ -967,7 +967,7 @@ </func> <func> - <name>print(Format) -> ok</name> + <name since="">print(Format) -> ok</name> <fsummary>Equivalent to print(default, 50, Format, [], []).</fsummary> <desc><marker id="print-1"/> <p>Equivalent to <seealso marker="#print-5"><c>ct:print(default, @@ -976,7 +976,7 @@ </func> <func> - <name>print(X1, X2) -> ok</name> + <name since="OTP R15B02">print(X1, X2) -> ok</name> <fsummary>Equivalent to print(Category, Importance, Format, FormatArgs, []).</fsummary> <type> @@ -990,7 +990,7 @@ </func> <func> - <name>print(X1, X2, X3) -> ok</name> + <name since="">print(X1, X2, X3) -> ok</name> <fsummary>Equivalent to print(Category, Importance, Format, FormatArgs, Opts).</fsummary> <type> @@ -1005,7 +1005,7 @@ </func> <func> - <name>print(X1, X2, X3, X4) -> ok</name> + <name since="OTP R15B02">print(X1, X2, X3, X4) -> ok</name> <fsummary>Equivalent to print(Category, Importance, Format, FormatArgs, Opts).</fsummary> <type> @@ -1021,7 +1021,7 @@ </func> <func> - <name>print(Category, Importance, Format, FormatArgs, Opts) -> ok</name> + <name since="OTP 19.2">print(Category, Importance, Format, FormatArgs, Opts) -> ok</name> <fsummary>Prints from a test case to the console.</fsummary> <type> <v>Category = atom()</v> @@ -1048,7 +1048,7 @@ </func> <func> - <name>reload_config(Required) -> ValueOrElement | {error, Reason}</name> + <name since="OTP R14B">reload_config(Required) -> ValueOrElement | {error, Reason}</name> <fsummary>Reloads configuration file containing specified configuration key.</fsummary> <type> @@ -1071,7 +1071,7 @@ </func> <func> - <name>remaining_test_procs() -> {TestProcs,SharedGL,OtherGLs}</name> + <name since="OTP 20.2">remaining_test_procs() -> {TestProcs,SharedGL,OtherGLs}</name> <fsummary>>This function will return the identity of test- and group leader processes that are still running at the time of this call.</fsummary> <type> @@ -1107,7 +1107,7 @@ </func> <func> - <name>remove_config(Callback, Config) -> ok</name> + <name since="OTP R14B">remove_config(Callback, Config) -> ok</name> <fsummary>Removes configuration variables (together with their aliases) that were loaded with specified callback module and configuration string.</fsummary> @@ -1124,7 +1124,7 @@ </func> <func> - <name>require(Required) -> ok | {error, Reason}</name> + <name since="">require(Required) -> ok | {error, Reason}</name> <fsummary>Checks if the required configuration is available.</fsummary> <type> <v>Required = Key | {Key, SubKeys} | {Key, SubKey, SubKeys}</v> @@ -1178,7 +1178,7 @@ </func> <func> - <name>require(Name, Required) -> ok | {error, Reason}</name> + <name since="">require(Name, Required) -> ok | {error, Reason}</name> <fsummary>Checks if the required configuration is available and gives it a name.</fsummary> <type> @@ -1237,7 +1237,7 @@ </func> <func> - <name>run(TestDirs) -> Result</name> + <name since="">run(TestDirs) -> Result</name> <fsummary>Runs all test cases in all suites in the specified directories.</fsummary> <type> @@ -1251,7 +1251,7 @@ </func> <func> - <name>run(TestDir, Suite) -> Result</name> + <name since="">run(TestDir, Suite) -> Result</name> <fsummary>Runs all test cases in the specified suite.</fsummary> <desc><marker id="run-2"/> <p>Runs all test cases in the specified suite.</p> @@ -1261,7 +1261,7 @@ </func> <func> - <name>run(TestDir, Suite, Cases) -> Result</name> + <name since="">run(TestDir, Suite, Cases) -> Result</name> <fsummary>Runs the specified test cases.</fsummary> <type> <v>TestDir = string()</v> @@ -1283,7 +1283,7 @@ </func> <func> - <name>run_test(Opts) -> Result</name> + <name since="">run_test(Opts) -> Result</name> <fsummary>Runs tests as specified by the combination of options in Opts.</fsummary> <type> @@ -1355,7 +1355,7 @@ </func> <func> - <name>run_testspec(TestSpec) -> Result</name> + <name since="">run_testspec(TestSpec) -> Result</name> <fsummary>Runs a test specified by TestSpec.</fsummary> <type> <v>TestSpec = [term()]</v> @@ -1375,7 +1375,7 @@ </func> <func> - <name>set_verbosity(Category, Level) -> ok</name> + <name since="OTP 19.1">set_verbosity(Category, Level) -> ok</name> <fsummary>Set the verbosity level for a logging category.</fsummary> <type> <v>Category = default | atom()</v> @@ -1390,7 +1390,7 @@ </func> <func> - <name>sleep(Time) -> ok</name> + <name since="OTP R14B">sleep(Time) -> ok</name> <fsummary>This function, similar to timer:sleep/1, suspends the test case for a specified time.</fsummary> <type> @@ -1412,7 +1412,7 @@ </func> <func> - <name>start_interactive() -> ok</name> + <name since="">start_interactive() -> ok</name> <fsummary>Starts <c>Common Test</c> in interactive mode.</fsummary> <desc><marker id="start_interactive-0"/> <p>Starts <c>Common Test</c> in interactive mode.</p> @@ -1440,7 +1440,7 @@ </func> <func> - <name>step(TestDir, Suite, Case) -> Result</name> + <name since="">step(TestDir, Suite, Case) -> Result</name> <fsummary>Steps through a test case with the debugger.</fsummary> <type> <v>Case = atom()</v> @@ -1453,7 +1453,7 @@ </func> <func> - <name>step(TestDir, Suite, Case, Opts) -> Result</name> + <name since="">step(TestDir, Suite, Case, Opts) -> Result</name> <fsummary>Steps through a test case with the debugger.</fsummary> <type> <v>Case = atom()</v> @@ -1470,7 +1470,7 @@ </func> <func> - <name>stop_interactive() -> ok</name> + <name since="">stop_interactive() -> ok</name> <fsummary>Exits the interactive mode.</fsummary> <desc><marker id="stop_interactive-0"/> <p>Exits the interactive mode.</p> @@ -1482,7 +1482,7 @@ </func> <func> - <name>sync_notify(Name, Data) -> ok</name> + <name since="OTP R15B02">sync_notify(Name, Data) -> ok</name> <fsummary>Sends a synchronous notification of type Name with Data to the <c>Common Test</c> event manager.</fsummary> <type> @@ -1501,7 +1501,7 @@ </func> <func> - <name>testcases(TestDir, Suite) -> Testcases | {error, Reason}</name> + <name since="">testcases(TestDir, Suite) -> Testcases | {error, Reason}</name> <fsummary>Returns all test cases in the specified suite.</fsummary> <type> <v>TestDir = string()</v> @@ -1515,7 +1515,7 @@ </func> <func> - <name>timetrap(Time) -> ok</name> + <name since="OTP R14B">timetrap(Time) -> ok</name> <fsummary>Sets a new timetrap for the running test case.</fsummary> <type> <v>Time = {hours, Hours} | {minutes, Mins} | {seconds, Secs} | Millisecs | infinity | Func</v> @@ -1539,7 +1539,7 @@ </func> <func> - <name>userdata(TestDir, Suite) -> SuiteUserData | {error, Reason}</name> + <name since="">userdata(TestDir, Suite) -> SuiteUserData | {error, Reason}</name> <fsummary>Returns any data specified with tag userdata in the list of tuples returned from Suite:suite/0.</fsummary> <type> @@ -1556,7 +1556,7 @@ </func> <func> - <name>userdata(TestDir, Suite, Case::GroupOrCase) -> TCUserData | {error, Reason}</name> + <name since="">userdata(TestDir, Suite, Case::GroupOrCase) -> TCUserData | {error, Reason}</name> <fsummary>Returns any data specified with tag userdata in the list of tuples returned from Suite:group(GroupName) or Suite:Case().</fsummary> <type> diff --git a/lib/common_test/doc/src/ct_cover.xml b/lib/common_test/doc/src/ct_cover.xml index 89d944acbe..61365d3522 100644 --- a/lib/common_test/doc/src/ct_cover.xml +++ b/lib/common_test/doc/src/ct_cover.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_cover.xml</file> </header> - <module>ct_cover</module> + <module since="">ct_cover</module> <modulesummary>Common Test framework code coverage support module. </modulesummary> @@ -47,7 +47,7 @@ <funcs> <func> - <name>add_nodes(Nodes) -> {ok, StartedNodes} | {error, Reason}</name> + <name since="">add_nodes(Nodes) -> {ok, StartedNodes} | {error, Reason}</name> <fsummary>Adds nodes to current cover test (only works if cover support is active).</fsummary> <type> @@ -67,7 +67,7 @@ </func> <func> - <name>cross_cover_analyse(Level, Tests) -> ok</name> + <name since="OTP R16B">cross_cover_analyse(Level, Tests) -> ok</name> <fsummary>Accumulates cover results over multiple tests.</fsummary> <type> <v>Level = overview | details</v> @@ -83,7 +83,7 @@ </func> <func> - <name>remove_nodes(Nodes) -> ok | {error, Reason}</name> + <name since="">remove_nodes(Nodes) -> ok | {error, Reason}</name> <fsummary>Removes nodes from the current cover test.</fsummary> <type> <v>Nodes = [atom()]</v> diff --git a/lib/common_test/doc/src/ct_ftp.xml b/lib/common_test/doc/src/ct_ftp.xml index 592c5eb05d..7ee6049486 100644 --- a/lib/common_test/doc/src/ct_ftp.xml +++ b/lib/common_test/doc/src/ct_ftp.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_ftp.xml</file> </header> - <module>ct_ftp</module> + <module since="">ct_ftp</module> <modulesummary>FTP client module (based on the FTP application).</modulesummary> <description> @@ -59,7 +59,7 @@ <funcs> <func> - <name>cd(Connection, Dir) -> ok | {error, Reason}</name> + <name since="">cd(Connection, Dir) -> ok | {error, Reason}</name> <fsummary>Changes directory on remote host.</fsummary> <type> <v>Connection = connection()</v> @@ -71,7 +71,7 @@ </func> <func> - <name>close(Connection) -> ok | {error, Reason}</name> + <name since="">close(Connection) -> ok | {error, Reason}</name> <fsummary>Closes the FTP connection.</fsummary> <type> <v>Connection = connection()</v> @@ -82,7 +82,7 @@ </func> <func> - <name>delete(Connection, File) -> ok | {error, Reason}</name> + <name since="">delete(Connection, File) -> ok | {error, Reason}</name> <fsummary>Deletes a file on remote host.</fsummary> <type> <v>Connection = connection()</v> @@ -94,7 +94,7 @@ </func> <func> - <name>get(KeyOrName, RemoteFile, LocalFile) -> ok | {error, Reason}</name> + <name since="">get(KeyOrName, RemoteFile, LocalFile) -> ok | {error, Reason}</name> <fsummary>Opens an FTP connection and fetches a file from the remote host.</fsummary> <type> @@ -122,7 +122,7 @@ </func> <func> - <name>ls(Connection, Dir) -> {ok, Listing} | {error, Reason}</name> + <name since="">ls(Connection, Dir) -> {ok, Listing} | {error, Reason}</name> <fsummary>Lists directory Dir.</fsummary> <type> <v>Connection = connection()</v> @@ -135,7 +135,7 @@ </func> <func> - <name>open(KeyOrName) -> {ok, Handle} | {error, Reason}</name> + <name since="">open(KeyOrName) -> {ok, Handle} | {error, Reason}</name> <fsummary>Opens an FTP connection to the specified node.</fsummary> <type> <v>KeyOrName = Key | Name</v> @@ -164,7 +164,7 @@ </func> <func> - <name>put(KeyOrName, LocalFile, RemoteFile) -> ok | {error, Reason}</name> + <name since="">put(KeyOrName, LocalFile, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Opens an FTP connection and sends a file to the remote host.</fsummary> <type> @@ -203,7 +203,7 @@ </func> <func> - <name>recv(Connection, RemoteFile) -> ok | {error, Reason}</name> + <name since="">recv(Connection, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Fetches a file over FTP.</fsummary> <desc><marker id="recv-2"/> <p>Fetches a file over FTP.</p> @@ -215,7 +215,7 @@ </func> <func> - <name>recv(Connection, RemoteFile, LocalFile) -> ok | {error, Reason}</name> + <name since="">recv(Connection, RemoteFile, LocalFile) -> ok | {error, Reason}</name> <fsummary>Fetches a file over FTP.</fsummary> <type> <v>Connection = connection()</v> @@ -230,7 +230,7 @@ </func> <func> - <name>send(Connection, LocalFile) -> ok | {error, Reason}</name> + <name since="">send(Connection, LocalFile) -> ok | {error, Reason}</name> <fsummary>Sends a file over FTP.</fsummary> <desc><marker id="send-2"/> <p>Sends a file over FTP.</p> @@ -243,7 +243,7 @@ </func> <func> - <name>send(Connection, LocalFile, RemoteFile) -> ok | {error, Reason}</name> + <name since="">send(Connection, LocalFile, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Sends a file over FTP.</fsummary> <type> <v>Connection = connection()</v> @@ -258,7 +258,7 @@ </func> <func> - <name>type(Connection, Type) -> ok | {error, Reason}</name> + <name since="">type(Connection, Type) -> ok | {error, Reason}</name> <fsummary>Changes the file transfer type.</fsummary> <type> <v>Connection = connection()</v> diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml index 2f853d133d..ff0d0117cd 100644 --- a/lib/common_test/doc/src/ct_hooks.xml +++ b/lib/common_test/doc/src/ct_hooks.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>ct_hooks.sgml</file> </header> - <module>ct_hooks</module> + <module since="OTP R14B02">ct_hooks</module> <modulesummary>A callback interface on top of Common Test.</modulesummary> <description> @@ -75,7 +75,7 @@ <funcs> <func> - <name>Module:init(Id, Opts) -> {ok, State} | {ok, State, Priority}</name> + <name since="OTP R14B02">Module:init(Id, Opts) -> {ok, State} | {ok, State, Priority}</name> <fsummary>Initiates the Common Test Hook.</fsummary> <type> <v>Id = reference() | term()</v> @@ -109,7 +109,7 @@ </func> <func> - <name>Module:pre_init_per_suite(SuiteName, InitData, CTHState) -> Result</name> + <name since="OTP R14B02">Module:pre_init_per_suite(SuiteName, InitData, CTHState) -> Result</name> <fsummary>Called before init_per_suite.</fsummary> <type> <v>SuiteName = atom()</v> @@ -161,7 +161,7 @@ </func> <func> - <name>Module:post_init_per_suite(SuiteName, Config, Return, CTHState) -> Result</name> + <name since="OTP R14B02">Module:post_init_per_suite(SuiteName, Config, Return, CTHState) -> Result</name> <fsummary>Called after init_per_suite.</fsummary> <type> <v>SuiteName = atom()</v> @@ -208,7 +208,7 @@ </func> <func> - <name>Module:pre_init_per_group(SuiteName, GroupName, InitData, CTHState) -> Result</name> + <name since="OTP 19.3">Module:pre_init_per_group(SuiteName, GroupName, InitData, CTHState) -> Result</name> <fsummary>Called before init_per_group.</fsummary> <type> <v>SuiteName = atom()</v> @@ -241,7 +241,7 @@ </func> <func> - <name>Module:post_init_per_group(SuiteName, GroupName, Config, Return, CTHState) -> Result</name> + <name since="OTP 19.3">Module:post_init_per_group(SuiteName, GroupName, Config, Return, CTHState) -> Result</name> <fsummary>Called after init_per_group.</fsummary> <type> <v>SuiteName = atom()</v> @@ -274,7 +274,7 @@ </func> <func> - <name>Module:pre_init_per_testcase(SuiteName, TestcaseName, InitData, CTHState) -> Result</name> + <name since="OTP 19.3">Module:pre_init_per_testcase(SuiteName, TestcaseName, InitData, CTHState) -> Result</name> <fsummary>Called before init_per_testcase.</fsummary> <type> <v>SuiteName = atom()</v> @@ -311,7 +311,7 @@ </func> <func> - <name>Module:post_init_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -> Result</name> + <name since="OTP 19.3">Module:post_init_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -> Result</name> <fsummary>Called after init_per_testcase.</fsummary> <type> <v>SuiteName = atom()</v> @@ -344,7 +344,7 @@ </func> <func> - <name>Module:pre_end_per_testcase(SuiteName, TestcaseName, EndData, CTHState) -> Result</name> + <name since="OTP 19.3">Module:pre_end_per_testcase(SuiteName, TestcaseName, EndData, CTHState) -> Result</name> <fsummary>Called before end_per_testcase.</fsummary> <type> <v>SuiteName = atom()</v> @@ -380,7 +380,7 @@ </func> <func> - <name>Module:post_end_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -> Result</name> + <name since="OTP 19.3">Module:post_end_per_testcase(SuiteName, TestcaseName, Config, Return, CTHState) -> Result</name> <fsummary>Called after end_per_testcase.</fsummary> <type> <v>SuiteName = atom()</v> @@ -413,7 +413,7 @@ </func> <func> - <name>Module:pre_end_per_group(SuiteName, GroupName, EndData, CTHState) -> Result</name> + <name since="OTP 19.3">Module:pre_end_per_group(SuiteName, GroupName, EndData, CTHState) -> Result</name> <fsummary>Called before end_per_group.</fsummary> <type> <v>SuiteName = atom()</v> @@ -446,7 +446,7 @@ </func> <func> - <name>Module:post_end_per_group(SuiteName, GroupName, Config, Return, CTHState) -> Result</name> + <name since="OTP 19.3">Module:post_end_per_group(SuiteName, GroupName, Config, Return, CTHState) -> Result</name> <fsummary>Called after end_per_group.</fsummary> <type> <v>SuiteName = atom()</v> @@ -479,7 +479,7 @@ </func> <func> - <name>Module:pre_end_per_suite(SuiteName, EndData, CTHState) -> Result</name> + <name since="OTP R14B02">Module:pre_end_per_suite(SuiteName, EndData, CTHState) -> Result</name> <fsummary>Called before end_per_suite.</fsummary> <type> <v>SuiteName = atom()</v> @@ -506,7 +506,7 @@ </func> <func> - <name>Module:post_end_per_suite(SuiteName, Config, Return, CTHState) -> Result</name> + <name since="OTP R14B02">Module:post_end_per_suite(SuiteName, Config, Return, CTHState) -> Result</name> <fsummary>Called after end_per_suite.</fsummary> <type> <v>SuiteName = atom()</v> @@ -533,7 +533,7 @@ </func> <func> - <name>Module:on_tc_fail(SuiteName, TestName, Reason, CTHState) -> NewCTHState</name> + <name since="OTP 19.3">Module:on_tc_fail(SuiteName, TestName, Reason, CTHState) -> NewCTHState</name> <fsummary>Called after the CTH scope ends.</fsummary> <type> <v>SuiteName = atom()</v> @@ -577,7 +577,7 @@ </func> <func> - <name>Module:on_tc_skip(SuiteName, TestName, Reason, CTHState) -> NewCTHState</name> + <name since="OTP 19.3">Module:on_tc_skip(SuiteName, TestName, Reason, CTHState) -> NewCTHState</name> <fsummary>Called after the CTH scope ends.</fsummary> <type> <v>SuiteName = atom()</v> @@ -623,7 +623,7 @@ </func> <func> - <name>Module:terminate(CTHState)</name> + <name since="OTP R14B02">Module:terminate(CTHState)</name> <fsummary>Called after the CTH scope ends.</fsummary> <type> <v>CTHState = term()</v> @@ -637,7 +637,7 @@ </func> <func> - <name>Module:id(Opts) -> Id</name> + <name since="OTP R14B02">Module:id(Opts) -> Id</name> <fsummary>Called before the init function of a CTH.</fsummary> <type> <v>Opts = term()</v> diff --git a/lib/common_test/doc/src/ct_master.xml b/lib/common_test/doc/src/ct_master.xml index 6bde4644c6..2ab421fe9e 100644 --- a/lib/common_test/doc/src/ct_master.xml +++ b/lib/common_test/doc/src/ct_master.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_master.xml</file> </header> - <module>ct_master</module> + <module since="">ct_master</module> <modulesummary>Distributed test execution control for Common Test.</modulesummary> <description> @@ -46,7 +46,7 @@ <funcs> <func> - <name>abort() -> ok</name> + <name since="">abort() -> ok</name> <fsummary>Stops all running tests.</fsummary> <desc><marker id="abort-0"/> <p>Stops all running tests.</p> @@ -54,7 +54,7 @@ </func> <func> - <name>abort(Nodes) -> ok</name> + <name since="">abort(Nodes) -> ok</name> <fsummary>Stops tests on specified nodes.</fsummary> <type> <v>Nodes = atom() | [atom()]</v> @@ -65,7 +65,7 @@ </func> <func> - <name>basic_html(Bool) -> ok</name> + <name since="OTP R15B01">basic_html(Bool) -> ok</name> <fsummary>If set to true, the ct_master logs are written on a primitive HTML format, not using the <c>Common Test</c> CSS style sheet.</fsummary> <type> @@ -79,7 +79,7 @@ </func> <func> - <name>get_event_mgr_ref() -> MasterEvMgrRef</name> + <name since="OTP 17.5">get_event_mgr_ref() -> MasterEvMgrRef</name> <fsummary>Gets a reference to the <c>Common Test</c> master event manager.</fsummary> <type> @@ -98,7 +98,7 @@ </func> <func> - <name>progress() -> [{Node, Status}]</name> + <name since="">progress() -> [{Node, Status}]</name> <fsummary>Returns test progress.</fsummary> <type> <v>Node = atom()</v> @@ -112,7 +112,7 @@ </func> <func> - <name>run(TestSpecs) -> ok</name> + <name since="">run(TestSpecs) -> ok</name> <fsummary>Equivalent to run(TestSpecs, false, [], []).</fsummary> <type> <v>TestSpecs = string() | [SeparateOrMerged]</v> @@ -124,7 +124,7 @@ </func> <func> - <name>run(TestSpecs, InclNodes, ExclNodes) -> ok</name> + <name since="">run(TestSpecs, InclNodes, ExclNodes) -> ok</name> <fsummary>Equivalent to run(TestSpecs, false, InclNodes, ExclNodes). </fsummary> <type> @@ -140,7 +140,7 @@ </func> <func> - <name>run(TestSpecs, AllowUserTerms, InclNodes, ExclNodes) -> ok</name> + <name since="">run(TestSpecs, AllowUserTerms, InclNodes, ExclNodes) -> ok</name> <fsummary>Tests are spawned on the nodes as specified in TestSpecs. </fsummary> <type> @@ -162,7 +162,7 @@ </func> <func> - <name>run_on_node(TestSpecs, Node) -> ok</name> + <name since="">run_on_node(TestSpecs, Node) -> ok</name> <fsummary>Equivalent to run_on_node(TestSpecs, false, Node).</fsummary> <type> <v>TestSpecs = string() | [SeparateOrMerged]</v> @@ -177,7 +177,7 @@ </func> <func> - <name>run_on_node(TestSpecs, AllowUserTerms, Node) -> ok</name> + <name since="">run_on_node(TestSpecs, AllowUserTerms, Node) -> ok</name> <fsummary>Tests are spawned on Node according to TestSpecs.</fsummary> <type> <v>TestSpecs = string() | [SeparateOrMerged]</v> @@ -191,7 +191,7 @@ </func> <func> - <name>run_test(Node, Opts) -> ok</name> + <name since="">run_test(Node, Opts) -> ok</name> <fsummary>Tests are spawned on Node using ct:run_test/1.</fsummary> <type> <v>Node = atom()</v> diff --git a/lib/common_test/doc/src/ct_netconfc.xml b/lib/common_test/doc/src/ct_netconfc.xml index 7ec8f23073..8fbe5f3df6 100644 --- a/lib/common_test/doc/src/ct_netconfc.xml +++ b/lib/common_test/doc/src/ct_netconfc.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_netconfc.xml</file> </header> - <module>ct_netconfc</module> + <module since="OTP R15B02">ct_netconfc</module> <modulesummary>NETCONF client module.</modulesummary> <description> @@ -312,8 +312,8 @@ <funcs> <func> - <name name="action" arity="2"/> - <name name="action" arity="3"/> + <name name="action" arity="2" since="OTP R15B02"/> + <name name="action" arity="3" since="OTP R15B02"/> <fsummary>Executes an action.</fsummary> <desc> <p>Executes an action. If the return type is void, <c>ok</c> is @@ -322,8 +322,8 @@ </func> <func> - <name name="close_session" arity="1"/> - <name name="close_session" arity="2"/> + <name name="close_session" arity="1" since="OTP R15B02"/> + <name name="close_session" arity="2" since="OTP R15B02"/> <fsummary>Requests graceful termination of the session associated with the client.</fsummary> <desc> @@ -339,7 +339,7 @@ </func> <func> - <name name="connect" arity="1"/> + <name name="connect" arity="1" since="OTP 20.0"/> <fsummary>Opens an SSH connection to a NETCONF server.</fsummary> <desc> <p>Opens an SSH connection to a NETCONF server.</p> @@ -361,7 +361,7 @@ </func> <func> - <name name="connect" arity="2"/> + <name name="connect" arity="2" since="OTP 20.0"/> <fsummary>Opens an SSH connection to a named NETCONF server.</fsummary> <desc> <p>Open an SSH connection to a named NETCONF server.</p> @@ -399,8 +399,8 @@ </func> <func> - <name name="copy_config" arity="3"/> - <name name="copy_config" arity="4"/> + <name name="copy_config" arity="3" since="OTP R15B02"/> + <name name="copy_config" arity="4" since="OTP R15B02"/> <fsummary>Copies configuration data.</fsummary> <desc> <p>Copies configuration data.</p> @@ -412,12 +412,12 @@ </func> <func> - <name>create_subscription(Client) -> Result</name> - <name>create_subscription(Client, Stream) -> Result</name> - <name>create_subscription(Client, Stream, Filter) -> Result</name> - <name>create_subscription(Client, Stream, Filter, Timeout) -> Result</name> - <name name="create_subscription" arity="5" clause_i="2"/> - <name name="create_subscription" arity="6"/> + <name since="OTP R15B02">create_subscription(Client) -> Result</name> + <name since="OTP R15B02">create_subscription(Client, Stream) -> Result</name> + <name since="OTP R15B02">create_subscription(Client, Stream, Filter) -> Result</name> + <name since="OTP R15B02">create_subscription(Client, Stream, Filter, Timeout) -> Result</name> + <name name="create_subscription" arity="5" clause_i="2" since="OTP R15B02"/> + <name name="create_subscription" arity="6" since="OTP R15B02"/> <fsummary>Creates a subscription for event notifications.</fsummary> <desc> <p>Creates a subscription for event notifications.</p> @@ -490,8 +490,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="delete_config" arity="2"/> - <name name="delete_config" arity="3"/> + <name name="delete_config" arity="2" since="OTP R15B02"/> + <name name="delete_config" arity="3" since="OTP R15B02"/> <fsummary>Deletes configuration data.</fsummary> <desc> <p>Deletes configuration data.</p> @@ -502,7 +502,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="disconnect" arity="1"/> + <name name="disconnect" arity="1" since="OTP 20.0"/> <fsummary>Closes the given SSH connection.</fsummary> <desc> <p>Closes the given SSH connection.</p> @@ -514,10 +514,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="edit_config" arity="3"/> - <name name="edit_config" arity="4" clause_i="1"/> - <name name="edit_config" arity="4" clause_i="2"/> - <name name="edit_config" arity="5"/> + <name name="edit_config" arity="3" since="OTP R15B02"/> + <name name="edit_config" arity="4" clause_i="1" since="OTP 18.0"/> + <name name="edit_config" arity="4" clause_i="2" since="OTP R15B02"/> + <name name="edit_config" arity="5" since="OTP 18.0"/> <fsummary>Edits configuration data.</fsummary> <desc> <p>Edits configuration data.</p> @@ -542,8 +542,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="get" arity="2"/> - <name name="get" arity="3"/> + <name name="get" arity="2" since="OTP R15B02"/> + <name name="get" arity="3" since="OTP R15B02"/> <fsummary>Gets data.</fsummary> <desc> <p>Gets data.</p> @@ -557,8 +557,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="get_capabilities" arity="1"/> - <name name="get_capabilities" arity="2"/> + <name name="get_capabilities" arity="1" since="OTP R15B02"/> + <name name="get_capabilities" arity="2" since="OTP R15B02"/> <fsummary>Returns the server side capabilities.</fsummary> <desc> <p>Returns the server side capabilities.</p> @@ -582,8 +582,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="get_config" arity="3"/> - <name name="get_config" arity="4"/> + <name name="get_config" arity="3" since="OTP R15B02"/> + <name name="get_config" arity="4" since="OTP R15B02"/> <fsummary>Gets configuration data.</fsummary> <desc> <p>Gets configuration data.</p> @@ -597,10 +597,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="get_event_streams" arity="1"/> - <name name="get_event_streams" arity="2" clause_i="1"/> - <name name="get_event_streams" arity="2" clause_i="2"/> - <name name="get_event_streams" arity="3"/> + <name name="get_event_streams" arity="1" since="OTP 20.0"/> + <name name="get_event_streams" arity="2" clause_i="1" since="OTP R15B02"/> + <name name="get_event_streams" arity="2" clause_i="2" since="OTP 20.0"/> + <name name="get_event_streams" arity="3" since="OTP R15B02"/> <fsummary>Sends a request to get the specified event streams.</fsummary> <desc> <p>Sends a request to get the specified event streams.</p> @@ -637,8 +637,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="get_session_id" arity="1"/> - <name name="get_session_id" arity="2"/> + <name name="get_session_id" arity="1" since="OTP R15B02"/> + <name name="get_session_id" arity="2" since="OTP R15B02"/> <fsummary>Returns the session Id associated with the specified client.</fsummary> <desc> @@ -647,9 +647,9 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="hello" arity="1"/> - <name name="hello" arity="2"/> - <name name="hello" arity="3"/> + <name name="hello" arity="1" since="OTP R15B02"/> + <name name="hello" arity="2" since="OTP R15B02"/> + <name name="hello" arity="3" since="OTP 17.5.3"/> <fsummary>Exchanges hello messages with the server.</fsummary> <desc> <p>Exchanges <c>hello</c> messages with the server.</p> @@ -660,8 +660,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="kill_session" arity="2"/> - <name name="kill_session" arity="3"/> + <name name="kill_session" arity="2" since="OTP R15B02"/> + <name name="kill_session" arity="3" since="OTP R15B02"/> <fsummary>Forces termination of the session associated with the supplied session Id.</fsummary> <desc> @@ -682,8 +682,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="lock" arity="2"/> - <name name="lock" arity="3"/> + <name name="lock" arity="2" since="OTP R15B02"/> + <name name="lock" arity="3" since="OTP R15B02"/> <fsummary>Locks the configuration target.</fsummary> <desc> <p>Locks the configuration target.</p> @@ -703,7 +703,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="only_open" arity="1"/> + <name name="only_open" arity="1" since="OTP R15B02"/> <fsummary>Opens a NETCONF session, but does not send hello.</fsummary> <desc> <p>Opens a NETCONF session, but does not send <c>hello</c>.</p> @@ -714,7 +714,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="only_open" arity="2"/> + <name name="only_open" arity="2" since="OTP R15B02"/> <fsummary>Opens a named NETCONF session, but does not send hello.</fsummary> <desc> <p>Opens a named NETCONF session, but does not send <c>hello</c>.</p> @@ -725,7 +725,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="open" arity="1"/> + <name name="open" arity="1" since="OTP R15B02"/> <fsummary>Opens a NETCONF session and exchanges hello messages.</fsummary> <desc> <p>Opens a NETCONF session and exchanges <c>hello</c> messages.</p> @@ -749,7 +749,7 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="open" arity="2"/> + <name name="open" arity="2" since="OTP R15B02"/> <fsummary>Opens a named NETCONF session and exchanges hello messages.</fsummary> <desc> @@ -791,8 +791,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="send" arity="2"/> - <name name="send" arity="3"/> + <name name="send" arity="2" since="OTP R16B02"/> + <name name="send" arity="3" since="OTP R16B02"/> <fsummary>Sends an XML document to the server.</fsummary> <desc> <p>Sends an XML document to the server.</p> @@ -804,8 +804,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="send_rpc" arity="2"/> - <name name="send_rpc" arity="3"/> + <name name="send_rpc" arity="2" since="OTP R16B02"/> + <name name="send_rpc" arity="3" since="OTP R16B02"/> <fsummary>Sends a NETCONF rpc request to the server.</fsummary> <desc> <p>Sends a NETCONF <c>rpc</c> request to the server.</p> @@ -820,10 +820,10 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="session" arity="1"/> - <name name="session" arity="2" clause_i="1"/> - <name name="session" arity="2" clause_i="2"/> - <name name="session" arity="3"/> + <name name="session" arity="1" since="OTP 20.0"/> + <name name="session" arity="2" clause_i="1" since="OTP 20.0"/> + <name name="session" arity="2" clause_i="2" since="OTP 20.0"/> + <name name="session" arity="3" since="OTP 20.0"/> <fsummary>Opens a NETCONF session as a channel on the given SSH connection, and exchanges hello messages with the server.</fsummary> @@ -848,8 +848,8 @@ create_subscription(Client, Stream, Filter, StartTime, StopTime, Timeout)</pre> </func> <func> - <name name="unlock" arity="2"/> - <name name="unlock" arity="3"/> + <name name="unlock" arity="2" since="OTP R15B02"/> + <name name="unlock" arity="3" since="OTP R15B02"/> <fsummary>Unlocks the configuration target.</fsummary> <desc> <p>Unlocks the configuration target.</p> diff --git a/lib/common_test/doc/src/ct_property_test.xml b/lib/common_test/doc/src/ct_property_test.xml index 028e5eb69f..1e01d9a5d7 100644 --- a/lib/common_test/doc/src/ct_property_test.xml +++ b/lib/common_test/doc/src/ct_property_test.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_property_test.xml</file> </header> - <module>ct_property_test</module> + <module since="OTP 17.3">ct_property_test</module> <modulesummary>EXPERIMENTAL support in Common Test for calling property-based tests.</modulesummary> @@ -79,7 +79,7 @@ <funcs> <func> - <name>init_per_suite(Config) -> Config | {skip, Reason}</name> + <name since="OTP 17.3">init_per_suite(Config) -> Config | {skip, Reason}</name> <fsummary>Initializes Config for property testing.</fsummary> <desc><marker id="init_per_suite-1"/> <p>Initializes <c>Config</c> for property testing.</p> @@ -98,7 +98,7 @@ </func> <func> - <name>quickcheck(Property, Config) -> true | {fail, Reason}</name> + <name since="OTP 17.3">quickcheck(Property, Config) -> true | {fail, Reason}</name> <fsummary>Calls quickcheck and returns the result in a form suitable for Common Test.</fsummary> <desc><marker id="quickcheck-2"/> diff --git a/lib/common_test/doc/src/ct_rpc.xml b/lib/common_test/doc/src/ct_rpc.xml index 90e6b833f7..00a4dcec08 100644 --- a/lib/common_test/doc/src/ct_rpc.xml +++ b/lib/common_test/doc/src/ct_rpc.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_rpc.xml</file> </header> - <module>ct_rpc</module> + <module since="">ct_rpc</module> <modulesummary>Common Test specific layer on Erlang/OTP rpc.</modulesummary> <description> @@ -43,7 +43,7 @@ <funcs> <func> - <name>app_node(App, Candidates) -> NodeName</name> + <name since="">app_node(App, Candidates) -> NodeName</name> <fsummary>From a set of candidate nodes determines which of them is running the application App.</fsummary> <type> @@ -61,7 +61,7 @@ </func> <func> - <name>app_node(App, Candidates, FailOnBadRPC) -> NodeName</name> + <name since="">app_node(App, Candidates, FailOnBadRPC) -> NodeName</name> <fsummary>Same as app_node/2, except that argument FailOnBadRPC determines if the search for a candidate node is to stop if badrpc is received at some point.</fsummary> @@ -81,7 +81,7 @@ </func> <func> - <name>app_node(App, Candidates, FailOnBadRPC, Cookie) -> NodeName</name> + <name since="">app_node(App, Candidates, FailOnBadRPC, Cookie) -> NodeName</name> <fsummary>Same as app_node/2, except that argument FailOnBadRPC determines if the search for a candidate node is to stop if badrpc is received at some point.</fsummary> @@ -105,7 +105,7 @@ </func> <func> - <name>call(Node, Module, Function, Args) -> term() | {badrpc, Reason}</name> + <name since="">call(Node, Module, Function, Args) -> term() | {badrpc, Reason}</name> <fsummary>Same as call(Node, Module, Function, Args, infinity).</fsummary> <desc><marker id="call-4"/> <p>Same as <c>call(Node, Module, Function, Args, infinity)</c>.</p> @@ -113,7 +113,7 @@ </func> <func> - <name>call(Node, Module, Function, Args, TimeOut) -> term() | {badrpc, Reason}</name> + <name since="">call(Node, Module, Function, Args, TimeOut) -> term() | {badrpc, Reason}</name> <fsummary>Evaluates apply(Module, Function, Args) on the node Node.</fsummary> <type> @@ -136,7 +136,7 @@ </func> <func> - <name>call(Node, Module, Function, Args, TimeOut, Cookie) -> term() | {badrpc, Reason}</name> + <name since="">call(Node, Module, Function, Args, TimeOut, Cookie) -> term() | {badrpc, Reason}</name> <fsummary>Evaluates apply(Module, Function, Args) on the node Node.</fsummary> <type> @@ -163,7 +163,7 @@ </func> <func> - <name>cast(Node, Module, Function, Args) -> ok</name> + <name since="">cast(Node, Module, Function, Args) -> ok</name> <fsummary>Evaluates apply(Module, Function, Args) on the node Node.</fsummary> <type> @@ -187,7 +187,7 @@ </func> <func> - <name>cast(Node, Module, Function, Args, Cookie) -> ok</name> + <name since="">cast(Node, Module, Function, Args, Cookie) -> ok</name> <fsummary>Evaluates apply(Module, Function, Args) on the node Node.</fsummary> <type> diff --git a/lib/common_test/doc/src/ct_slave.xml b/lib/common_test/doc/src/ct_slave.xml index 9d9aa50051..84e619482d 100644 --- a/lib/common_test/doc/src/ct_slave.xml +++ b/lib/common_test/doc/src/ct_slave.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_slave.xml</file> </header> - <module>ct_slave</module> + <module since="OTP R14B">ct_slave</module> <modulesummary>Common Test framework functions for starting and stopping nodes for Large-Scale Testing.</modulesummary> @@ -50,7 +50,7 @@ <funcs> <func> - <name>start(Node) -> Result</name> + <name since="OTP R14B">start(Node) -> Result</name> <fsummary>Starts an Erlang node with name Node on the local host.</fsummary> <type> @@ -68,7 +68,7 @@ </func> <func> - <name>start(HostOrNode, NodeOrOpts) -> Result</name> + <name since="OTP R14B">start(HostOrNode, NodeOrOpts) -> Result</name> <fsummary>Starts an Erlang node with default options on a specified host, or on the local host with specified options.</fsummary> <type> @@ -90,7 +90,7 @@ </func> <func> - <name>start(Host, Node, Opts) -> Result</name> + <name since="OTP R14B">start(Host, Node, Opts) -> Result</name> <fsummary>Starts an Erlang node with name Node on host Host as specified by the combination of options in Opts.</fsummary> <type> @@ -184,7 +184,7 @@ </func> <func> - <name>stop(Node) -> Result</name> + <name since="OTP R14B">stop(Node) -> Result</name> <fsummary>Stops the running Erlang node with name Node on the local host.</fsummary> <type> @@ -199,7 +199,7 @@ </func> <func> - <name>stop(Host, Node) -> Result</name> + <name since="OTP R14B">stop(Host, Node) -> Result</name> <fsummary>Stops the running Erlang node with name Node on host Host.</fsummary> <type> diff --git a/lib/common_test/doc/src/ct_snmp.xml b/lib/common_test/doc/src/ct_snmp.xml index 0a5e52b16c..343781814a 100644 --- a/lib/common_test/doc/src/ct_snmp.xml +++ b/lib/common_test/doc/src/ct_snmp.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_snmp.xml</file> </header> - <module>ct_snmp</module> + <module since="">ct_snmp</module> <modulesummary>Common Test user interface module for the SNMP application.</modulesummary> <description> @@ -240,7 +240,7 @@ <funcs> <func> - <name>get_next_values(Agent, Oids, MgrAgentConfName) -> SnmpReply</name> + <name since="">get_next_values(Agent, Oids, MgrAgentConfName) -> SnmpReply</name> <fsummary>Issues a synchronous SNMP get next request.</fsummary> <type> <v>Agent = agent_name()</v> @@ -254,7 +254,7 @@ </func> <func> - <name>get_values(Agent, Oids, MgrAgentConfName) -> SnmpReply</name> + <name since="">get_values(Agent, Oids, MgrAgentConfName) -> SnmpReply</name> <fsummary>Issues a synchronous SNMP get request.</fsummary> <type> <v>Agent = agent_name()</v> @@ -268,7 +268,7 @@ </func> <func> - <name>load_mibs(Mibs) -> ok | {error, Reason}</name> + <name since="">load_mibs(Mibs) -> ok | {error, Reason}</name> <fsummary>Loads the MIBs into agent snmp_master_agent.</fsummary> <type> <v>Mibs = [MibName]</v> @@ -281,7 +281,7 @@ </func> <func> - <name>register_agents(MgrAgentConfName, ManagedAgents) -> ok | {error, Reason}</name> + <name since="">register_agents(MgrAgentConfName, ManagedAgents) -> ok | {error, Reason}</name> <fsummary>Explicitly instructs the manager to handle this agent.</fsummary> <type> @@ -300,7 +300,7 @@ </func> <func> - <name>register_users(MgrAgentConfName, Users) -> ok | {error, Reason}</name> + <name since="">register_users(MgrAgentConfName, Users) -> ok | {error, Reason}</name> <fsummary>Registers the manager entity (=user) responsible for specific agent(s).</fsummary> <type> @@ -319,7 +319,7 @@ </func> <func> - <name>register_usm_users(MgrAgentConfName, UsmUsers) -> ok | {error, Reason}</name> + <name since="">register_usm_users(MgrAgentConfName, UsmUsers) -> ok | {error, Reason}</name> <fsummary>Explicitly instructs the manager to handle this USM user.</fsummary> <type> <v>MgrAgentConfName = atom()</v> @@ -337,7 +337,7 @@ </func> <func> - <name>set_info(Config) -> [{Agent, OldVarsAndVals, NewVarsAndVals}]</name> + <name since="">set_info(Config) -> [{Agent, OldVarsAndVals, NewVarsAndVals}]</name> <fsummary>Returns a list of all successful set requests performed in the test case in reverse order.</fsummary> <type> @@ -357,7 +357,7 @@ </func> <func> - <name>set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -> SnmpReply</name> + <name since="">set_values(Agent, VarsAndVals, MgrAgentConfName, Config) -> SnmpReply</name> <fsummary>Issues a synchronous SNMP set request.</fsummary> <type> <v>Agent = agent_name()</v> @@ -372,7 +372,7 @@ </func> <func> - <name>start(Config, MgrAgentConfName) -> ok</name> + <name since="">start(Config, MgrAgentConfName) -> ok</name> <fsummary>Equivalent to start(Config, MgrAgentConfName, undefined).</fsummary> <desc><marker id="start-2"/> @@ -383,7 +383,7 @@ </func> <func> - <name>start(Config, MgrAgentConfName, SnmpAppConfName) -> ok</name> + <name since="">start(Config, MgrAgentConfName, SnmpAppConfName) -> ok</name> <fsummary>Starts an SNMP manager and/or agent.</fsummary> <type> <v>Config = [{Key, Value}]</v> @@ -415,7 +415,7 @@ </func> <func> - <name>stop(Config) -> ok</name> + <name since="">stop(Config) -> ok</name> <fsummary>Stops the SNMP manager and/or agent, and removes all files created.</fsummary> <type> @@ -430,7 +430,7 @@ </func> <func> - <name>unload_mibs(Mibs) -> ok | {error, Reason}</name> + <name since="OTP R16B">unload_mibs(Mibs) -> ok | {error, Reason}</name> <fsummary>Unloads the MIBs from agent snmp_master_agent.</fsummary> <type> <v>Mibs = [MibName]</v> @@ -443,7 +443,7 @@ </func> <func> - <name>unregister_agents(MgrAgentConfName) -> ok</name> + <name since="">unregister_agents(MgrAgentConfName) -> ok</name> <fsummary>Unregisters all managed agents.</fsummary> <type> <v>MgrAgentConfName = atom()</v> @@ -455,7 +455,7 @@ </func> <func> - <name>unregister_agents(MgrAgentConfName, ManagedAgents) -> ok</name> + <name since="OTP R16B">unregister_agents(MgrAgentConfName, ManagedAgents) -> ok</name> <fsummary>Unregisters the specified managed agents.</fsummary> <type> <v>MgrAgentConfName = atom()</v> @@ -468,7 +468,7 @@ </func> <func> - <name>unregister_users(MgrAgentConfName) -> ok</name> + <name since="">unregister_users(MgrAgentConfName) -> ok</name> <fsummary>Unregisters all users.</fsummary> <type> <v>MgrAgentConfName = atom()</v> @@ -480,7 +480,7 @@ </func> <func> - <name>unregister_users(MgrAgentConfName, Users) -> ok</name> + <name since="OTP R16B">unregister_users(MgrAgentConfName, Users) -> ok</name> <fsummary>Unregisters the specified users.</fsummary> <type> <v>MgrAgentConfName = atom()</v> @@ -493,7 +493,7 @@ </func> <func> - <name>unregister_usm_users(MgrAgentConfName) -> ok</name> + <name since="OTP R16B">unregister_usm_users(MgrAgentConfName) -> ok</name> <fsummary>Unregisters all USM users.</fsummary> <type> <v>MgrAgentConfName = atom()</v> @@ -505,7 +505,7 @@ </func> <func> - <name>unregister_usm_users(MgrAgentConfName, UsmUsers) -> ok</name> + <name since="OTP R16B">unregister_usm_users(MgrAgentConfName, UsmUsers) -> ok</name> <fsummary>Unregisters the specified USM users.</fsummary> <type> <v>MgrAgentConfName = atom()</v> diff --git a/lib/common_test/doc/src/ct_ssh.xml b/lib/common_test/doc/src/ct_ssh.xml index 0c7efed154..8d9f31aff8 100644 --- a/lib/common_test/doc/src/ct_ssh.xml +++ b/lib/common_test/doc/src/ct_ssh.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_ssh.xml</file> </header> - <module>ct_ssh</module> + <module since="">ct_ssh</module> <modulesummary>SSH/SFTP client module.</modulesummary> <description> @@ -95,7 +95,7 @@ <funcs> <func> - <name>apread(SSH, Handle, Position, Length) -> Result</name> + <name since="">apread(SSH, Handle, Position, Length) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -109,7 +109,7 @@ </func> <func> - <name>apread(SSH, Server, Handle, Position, Length) -> Result</name> + <name since="">apread(SSH, Server, Handle, Position, Length) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -123,7 +123,7 @@ </func> <func> - <name>apwrite(SSH, Handle, Position, Data) -> Result</name> + <name since="">apwrite(SSH, Handle, Position, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -137,7 +137,7 @@ </func> <func> - <name>apwrite(SSH, Server, Handle, Position, Data) -> Result</name> + <name since="">apwrite(SSH, Server, Handle, Position, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -151,7 +151,7 @@ </func> <func> - <name>aread(SSH, Handle, Len) -> Result</name> + <name since="">aread(SSH, Handle, Len) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -165,7 +165,7 @@ </func> <func> - <name>aread(SSH, Server, Handle, Len) -> Result</name> + <name since="">aread(SSH, Server, Handle, Len) -> Result</name> <fsummary>For inforamtion and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -179,7 +179,7 @@ </func> <func> - <name>awrite(SSH, Handle, Data) -> Result</name> + <name since="">awrite(SSH, Handle, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -193,7 +193,7 @@ </func> <func> - <name>awrite(SSH, Server, Handle, Data) -> Result</name> + <name since="">awrite(SSH, Server, Handle, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -207,7 +207,7 @@ </func> <func> - <name>close(SSH, Handle) -> Result</name> + <name since="">close(SSH, Handle) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -221,7 +221,7 @@ </func> <func> - <name>close(SSH, Server, Handle) -> Result</name> + <name since="">close(SSH, Server, Handle) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -235,7 +235,7 @@ </func> <func> - <name>connect(KeyOrName) -> {ok, Handle} | {error, Reason}</name> + <name since="">connect(KeyOrName) -> {ok, Handle} | {error, Reason}</name> <fsummary>Equivalent to connect(KeyOrName, host, []).</fsummary> <desc><marker id="connect-1"/> <p>Equivalent to @@ -245,7 +245,7 @@ </func> <func> - <name>connect(KeyOrName, ConnType) -> {ok, Handle} | {error, Reason}</name> + <name since="">connect(KeyOrName, ConnType) -> {ok, Handle} | {error, Reason}</name> <fsummary>Equivalent to connect(KeyOrName, ConnType, []).</fsummary> <desc><marker id="connect-2"/> <p>Equivalent to @@ -255,7 +255,7 @@ </func> <func> - <name>connect(KeyOrName, ConnType, ExtraOpts) -> {ok, Handle} | {error, Reason}</name> + <name since="">connect(KeyOrName, ConnType, ExtraOpts) -> {ok, Handle} | {error, Reason}</name> <fsummary>Opens an SSH or SFTP connection using the information associated with KeyOrName.</fsummary> <type> @@ -301,7 +301,7 @@ </func> <func> - <name>del_dir(SSH, Name) -> Result</name> + <name since="">del_dir(SSH, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -315,7 +315,7 @@ </func> <func> - <name>del_dir(SSH, Server, Name) -> Result</name> + <name since="">del_dir(SSH, Server, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -329,7 +329,7 @@ </func> <func> - <name>delete(SSH, Name) -> Result</name> + <name since="">delete(SSH, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -343,7 +343,7 @@ </func> <func> - <name>delete(SSH, Server, Name) -> Result</name> + <name since="">delete(SSH, Server, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -357,7 +357,7 @@ </func> <func> - <name>disconnect(SSH) -> ok | {error, Reason}</name> + <name since="">disconnect(SSH) -> ok | {error, Reason}</name> <fsummary>Closes an SSH/SFTP connection.</fsummary> <type> <v>SSH = connection()</v> @@ -369,7 +369,7 @@ </func> <func> - <name>exec(SSH, Command) -> {ok, Data} | {error, Reason}</name> + <name since="">exec(SSH, Command) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to exec(SSH, Command, DefaultTimeout).</fsummary> <desc><marker id="exec-2"/> <p>Equivalent to @@ -379,7 +379,7 @@ </func> <func> - <name>exec(SSH, Command, Timeout) -> {ok, Data} | {error, Reason}</name> + <name since="">exec(SSH, Command, Timeout) -> {ok, Data} | {error, Reason}</name> <fsummary>Requests server to perform Command.</fsummary> <type> <v>SSH = connection()</v> @@ -396,7 +396,7 @@ </func> <func> - <name>exec(SSH, ChannelId, Command, Timeout) -> {ok, Data} | {error, Reason}</name> + <name since="">exec(SSH, ChannelId, Command, Timeout) -> {ok, Data} | {error, Reason}</name> <fsummary>Requests server to perform Command.</fsummary> <type> <v>SSH = connection()</v> @@ -414,7 +414,7 @@ </func> <func> - <name>get_file_info(SSH, Handle) -> Result</name> + <name since="">get_file_info(SSH, Handle) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -428,7 +428,7 @@ </func> <func> - <name>get_file_info(SSH, Server, Handle) -> Result</name> + <name since="">get_file_info(SSH, Server, Handle) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -442,7 +442,7 @@ </func> <func> - <name>list_dir(SSH, Path) -> Result</name> + <name since="">list_dir(SSH, Path) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -456,7 +456,7 @@ </func> <func> - <name>list_dir(SSH, Server, Path) -> Result</name> + <name since="">list_dir(SSH, Server, Path) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -470,7 +470,7 @@ </func> <func> - <name>make_dir(SSH, Name) -> Result</name> + <name since="">make_dir(SSH, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -484,7 +484,7 @@ </func> <func> - <name>make_dir(SSH, Server, Name) -> Result</name> + <name since="">make_dir(SSH, Server, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -498,7 +498,7 @@ </func> <func> - <name>make_symlink(SSH, Name, Target) -> Result</name> + <name since="">make_symlink(SSH, Name, Target) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -512,7 +512,7 @@ </func> <func> - <name>make_symlink(SSH, Server, Name, Target) -> Result</name> + <name since="">make_symlink(SSH, Server, Name, Target) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -526,7 +526,7 @@ </func> <func> - <name>open(SSH, File, Mode) -> Result</name> + <name since="">open(SSH, File, Mode) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -540,7 +540,7 @@ </func> <func> - <name>open(SSH, Server, File, Mode) -> Result</name> + <name since="">open(SSH, Server, File, Mode) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -554,7 +554,7 @@ </func> <func> - <name>opendir(SSH, Path) -> Result</name> + <name since="">opendir(SSH, Path) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -568,7 +568,7 @@ </func> <func> - <name>opendir(SSH, Server, Path) -> Result</name> + <name since="">opendir(SSH, Server, Path) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -582,7 +582,7 @@ </func> <func> - <name>position(SSH, Handle, Location) -> Result</name> + <name since="">position(SSH, Handle, Location) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -596,7 +596,7 @@ </func> <func> - <name>position(SSH, Server, Handle, Location) -> Result</name> + <name since="">position(SSH, Server, Handle, Location) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -610,7 +610,7 @@ </func> <func> - <name>pread(SSH, Handle, Position, Length) -> Result</name> + <name since="">pread(SSH, Handle, Position, Length) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -624,7 +624,7 @@ </func> <func> - <name>pread(SSH, Server, Handle, Position, Length) -> Result</name> + <name since="">pread(SSH, Server, Handle, Position, Length) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -638,7 +638,7 @@ </func> <func> - <name>pwrite(SSH, Handle, Position, Data) -> Result</name> + <name since="">pwrite(SSH, Handle, Position, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -652,7 +652,7 @@ </func> <func> - <name>pwrite(SSH, Server, Handle, Position, Data) -> Result</name> + <name since="">pwrite(SSH, Server, Handle, Position, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -666,7 +666,7 @@ </func> <func> - <name>read(SSH, Handle, Len) -> Result</name> + <name since="">read(SSH, Handle, Len) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -680,7 +680,7 @@ </func> <func> - <name>read(SSH, Server, Handle, Len) -> Result</name> + <name since="">read(SSH, Server, Handle, Len) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -694,7 +694,7 @@ </func> <func> - <name>read_file(SSH, File) -> Result</name> + <name since="">read_file(SSH, File) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -708,7 +708,7 @@ </func> <func> - <name>read_file(SSH, Server, File) -> Result</name> + <name since="">read_file(SSH, Server, File) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -722,7 +722,7 @@ </func> <func> - <name>read_file_info(SSH, Name) -> Result</name> + <name since="">read_file_info(SSH, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -736,7 +736,7 @@ </func> <func> - <name>read_file_info(SSH, Server, Name) -> Result</name> + <name since="">read_file_info(SSH, Server, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -750,7 +750,7 @@ </func> <func> - <name>read_link(SSH, Name) -> Result</name> + <name since="">read_link(SSH, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -764,7 +764,7 @@ </func> <func> - <name>read_link(SSH, Server, Name) -> Result</name> + <name since="">read_link(SSH, Server, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -778,7 +778,7 @@ </func> <func> - <name>read_link_info(SSH, Name) -> Result</name> + <name since="">read_link_info(SSH, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -792,7 +792,7 @@ </func> <func> - <name>read_link_info(SSH, Server, Name) -> Result</name> + <name since="">read_link_info(SSH, Server, Name) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -806,7 +806,7 @@ </func> <func> - <name>receive_response(SSH, ChannelId) -> {ok, Data} | {error, Reason}</name> + <name since="">receive_response(SSH, ChannelId) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to receive_response(SSH, ChannelId, close).</fsummary> <desc><marker id="receive_response-2"/> @@ -817,7 +817,7 @@ ChannelId, close)</c></seealso>.</p> </func> <func> - <name>receive_response(SSH, ChannelId, End) -> {ok, Data} | {error, Reason}</name> + <name since="">receive_response(SSH, ChannelId, End) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to receive_response(SSH, ChannelId, End, DefaultTimeout).</fsummary> <desc><marker id="receive_response-3"/> @@ -828,7 +828,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>receive_response(SSH, ChannelId, End, Timeout) -> {ok, Data} | {timeout, Data} | {error, Reason}</name> + <name since="">receive_response(SSH, ChannelId, End, Timeout) -> {ok, Data} | {timeout, Data} | {error, Reason}</name> <fsummary>Receives expected data from server on the specified session channel.</fsummary> <type> @@ -863,7 +863,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>rename(SSH, OldName, NewName) -> Result</name> + <name since="">rename(SSH, OldName, NewName) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -877,7 +877,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>rename(SSH, Server, OldName, NewName) -> Result</name> + <name since="">rename(SSH, Server, OldName, NewName) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -891,7 +891,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>send(SSH, ChannelId, Data) -> ok | {error, Reason}</name> + <name since="">send(SSH, ChannelId, Data) -> ok | {error, Reason}</name> <fsummary>Equivalent to send(SSH, ChannelId, 0, Data, DefaultTimeout).</fsummary> <desc><marker id="send-3"/> @@ -901,7 +901,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>send(SSH, ChannelId, Data, Timeout) -> ok | {error, Reason}</name> + <name since="">send(SSH, ChannelId, Data, Timeout) -> ok | {error, Reason}</name> <fsummary>Equivalent to send(SSH, ChannelId, 0, Data, Timeout).</fsummary> <desc><marker id="send-4"/> <p>Equivalent to <seealso marker="#send-5"><c>ct_ssh:send(SSH, @@ -910,7 +910,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>send(SSH, ChannelId, Type, Data, Timeout) -> ok | {error, Reason}</name> + <name since="">send(SSH, ChannelId, Type, Data, Timeout) -> ok | {error, Reason}</name> <fsummary>Sends data to server on specified session channel.</fsummary> <type> <v>SSH = connection()</v> @@ -926,7 +926,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>send_and_receive(SSH, ChannelId, Data) -> {ok, Data} | {error, Reason}</name> + <name since="">send_and_receive(SSH, ChannelId, Data) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to send_and_receive(SSH, ChannelId, Data, close).</fsummary> <desc><marker id="send_and_receive-3"/> @@ -937,7 +937,7 @@ ChannelId, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>send_and_receive(SSH, ChannelId, Data, End) -> {ok, Data} | {error, Reason}</name> + <name since="">send_and_receive(SSH, ChannelId, Data, End) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to send_and_receive(SSH, ChannelId, 0, Data, End, DefaultTimeout).</fsummary> <desc><marker id="send_and_receive-4"/> @@ -948,7 +948,7 @@ ChannelId, 0, Data, End, DefaultTimeout)</c></seealso>.</p> </func> <func> - <name>send_and_receive(SSH, ChannelId, Data, End, Timeout) -> {ok, Data} | {error, Reason}</name> + <name since="">send_and_receive(SSH, ChannelId, Data, End, Timeout) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to send_and_receive(SSH, ChannelId, 0, Data, End, Timeout).</fsummary> <desc><marker id="send_and_receive-5"/> @@ -959,7 +959,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) -> {ok, Data} | {error, Reason}</name> + <name since="">send_and_receive(SSH, ChannelId, Type, Data, End, Timeout) -> {ok, Data} | {error, Reason}</name> <fsummary>Sends data to server on specified session channel and waits to receive the server response.</fsummary> <type> @@ -981,7 +981,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>session_close(SSH, ChannelId) -> ok | {error, Reason}</name> + <name since="">session_close(SSH, ChannelId) -> ok | {error, Reason}</name> <fsummary>Closes an SSH session channel.</fsummary> <type> <v>SSH = connection()</v> @@ -994,7 +994,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>session_open(SSH) -> {ok, ChannelId} | {error, Reason}</name> + <name since="">session_open(SSH) -> {ok, ChannelId} | {error, Reason}</name> <fsummary>Equivalent to session_open(SSH, DefaultTimeout).</fsummary> <desc><marker id="session_open-1"/> <p>Equivalent to @@ -1004,7 +1004,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>session_open(SSH, Timeout) -> {ok, ChannelId} | {error, Reason}</name> + <name since="">session_open(SSH, Timeout) -> {ok, ChannelId} | {error, Reason}</name> <fsummary>Opens a channel for an SSH session.</fsummary> <type> <v>SSH = connection()</v> @@ -1018,7 +1018,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>sftp_connect(SSH) -> {ok, Server} | {error, Reason}</name> + <name since="">sftp_connect(SSH) -> {ok, Server} | {error, Reason}</name> <fsummary>Starts an SFTP session on an already existing SSH connection.</fsummary> <type> @@ -1034,7 +1034,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>shell(SSH, ChannelId) -> ok | {error, Reason}</name> + <name since="OTP 20.0">shell(SSH, ChannelId) -> ok | {error, Reason}</name> <fsummary>Equivalent to shell(SSH, ChannelId, DefaultTimeout).</fsummary> <desc><marker id="shell-2"/> <p>Equivalent to @@ -1044,7 +1044,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>shell(SSH, ChannelId, Timeout) -> ok | {error, Reason}</name> + <name since="OTP 20.0">shell(SSH, ChannelId, Timeout) -> ok | {error, Reason}</name> <fsummary>Requests that the user default shell is executed at the server end.</fsummary> <type> @@ -1061,7 +1061,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>subsystem(SSH, ChannelId, Subsystem) -> Status | {error, Reason}</name> + <name since="">subsystem(SSH, ChannelId, Subsystem) -> Status | {error, Reason}</name> <fsummary>Equivalent to subsystem(SSH, ChannelId, Subsystem, DefaultTimeout).</fsummary> <desc><marker id="subsystem-3"/> @@ -1072,7 +1072,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>subsystem(SSH, ChannelId, Subsystem, Timeout) -> Status | {error, Reason}</name> + <name since="">subsystem(SSH, ChannelId, Subsystem, Timeout) -> Status | {error, Reason}</name> <fsummary>Sends a request to execute a predefined subsystem.</fsummary> <type> <v>SSH = connection()</v> @@ -1088,7 +1088,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>write(SSH, Handle, Data) -> Result</name> + <name since="">write(SSH, Handle, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -1102,7 +1102,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>write(SSH, Server, Handle, Data) -> Result</name> + <name since="">write(SSH, Server, Handle, Data) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -1116,7 +1116,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>write_file(SSH, File, Iolist) -> Result</name> + <name since="">write_file(SSH, File, Iolist) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -1130,7 +1130,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>write_file(SSH, Server, File, Iolist) -> Result</name> + <name since="">write_file(SSH, Server, File, Iolist) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -1144,7 +1144,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>write_file_info(SSH, Name, Info) -> Result</name> + <name since="">write_file_info(SSH, Name, Info) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> @@ -1158,7 +1158,7 @@ ChannelId, 0, Data, End, Timeout)</c></seealso>.</p> </func> <func> - <name>write_file_info(SSH, Server, Name, Info) -> Result</name> + <name since="">write_file_info(SSH, Server, Name, Info) -> Result</name> <fsummary>For information and other types, see ssh_sftp(3).</fsummary> <type> <v>SSH = connection()</v> diff --git a/lib/common_test/doc/src/ct_telnet.xml b/lib/common_test/doc/src/ct_telnet.xml index 8e85cccc99..9a12ce79ed 100644 --- a/lib/common_test/doc/src/ct_telnet.xml +++ b/lib/common_test/doc/src/ct_telnet.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_telnet.xml</file> </header> - <module>ct_telnet</module> + <module since="">ct_telnet</module> <modulesummary>Common Test specific layer on top of Telnet client ct_telnet_client.erl</modulesummary> <description> @@ -205,7 +205,7 @@ <funcs> <func> - <name>close(Connection) -> ok | {error, Reason}</name> + <name since="">close(Connection) -> ok | {error, Reason}</name> <fsummary>Closes the Telnet connection and stops the process managing it.</fsummary> <type> @@ -223,7 +223,7 @@ </func> <func> - <name>cmd(Connection, Cmd) -> {ok, Data} | {error, Reason}</name> + <name since="">cmd(Connection, Cmd) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to cmd(Connection, Cmd, []).</fsummary> <desc><marker id="cmd-2"/> <p>Equivalent to @@ -233,7 +233,7 @@ </func> <func> - <name>cmd(Connection, Cmd, Opts) -> {ok, Data} | {error, Reason}</name> + <name since="">cmd(Connection, Cmd, Opts) -> {ok, Data} | {error, Reason}</name> <fsummary>Sends a command through Telnet and waits for prompt.</fsummary> <type> <v>Connection = connection()</v> @@ -262,7 +262,7 @@ </func> <func> - <name>cmdf(Connection, CmdFormat, Args) -> {ok, Data} | {error, Reason}</name> + <name since="">cmdf(Connection, CmdFormat, Args) -> {ok, Data} | {error, Reason}</name> <fsummary>Equivalent to cmdf(Connection, CmdFormat, Args, []).</fsummary> <desc><marker id="cmdf-3"/> <p>Equivalent to @@ -272,7 +272,7 @@ </func> <func> - <name>cmdf(Connection, CmdFormat, Args, Opts) -> {ok, Data} | {error, Reason}</name> + <name since="">cmdf(Connection, CmdFormat, Args, Opts) -> {ok, Data} | {error, Reason}</name> <fsummary>Sends a Telnet command and waits for prompt (uses a format string and a list of arguments to build the command).</fsummary> <type> @@ -294,7 +294,7 @@ </func> <func> - <name>expect(Connection, Patterns) -> term()</name> + <name since="">expect(Connection, Patterns) -> term()</name> <fsummary>Equivalent to expect(Connections, Patterns, []).</fsummary> <desc><marker id="expect-2"/> <p>Equivalent to @@ -304,7 +304,7 @@ </func> <func> - <name>expect(Connection, Patterns, Opts) -> {ok, Match} | {ok, MatchList, HaltReason} | {error, Reason}</name> + <name since="">expect(Connection, Patterns, Opts) -> {ok, Match} | {ok, MatchList, HaltReason} | {error, Reason}</name> <fsummary>Gets data from Telnet and waits for the expected pattern.</fsummary> <type> @@ -422,7 +422,7 @@ </func> <func> - <name>get_data(Connection) -> {ok, Data} | {error, Reason}</name> + <name since="">get_data(Connection) -> {ok, Data} | {error, Reason}</name> <fsummary>Gets all data received by the Telnet client since the last command was sent.</fsummary> <type> @@ -446,7 +446,7 @@ </func> <func> - <name>open(Name) -> {ok, Handle} | {error, Reason}</name> + <name since="">open(Name) -> {ok, Handle} | {error, Reason}</name> <fsummary>Equivalent to open(Name, telnet).</fsummary> <desc><marker id="open-1"/> <p>Equivalent to @@ -456,7 +456,7 @@ </func> <func> - <name>open(Name, ConnType) -> {ok, Handle} | {error, Reason}</name> + <name since="">open(Name, ConnType) -> {ok, Handle} | {error, Reason}</name> <fsummary>Opens a Telnet connection to the specified target host.</fsummary> <type> @@ -471,7 +471,7 @@ </func> <func> - <name>open(KeyOrName, ConnType, TargetMod) -> {ok, Handle} | {error, Reason}</name> + <name since="">open(KeyOrName, ConnType, TargetMod) -> {ok, Handle} | {error, Reason}</name> <fsummary>Equivalent to open(KeyOrName, ConnType, TargetMod, []).</fsummary> <desc><marker id="open-3"/> <p>Equivalent to @@ -481,7 +481,7 @@ </func> <func> - <name>open(KeyOrName, ConnType, TargetMod, Extra) -> {ok, Handle} | {error, Reason}</name> + <name since="">open(KeyOrName, ConnType, TargetMod, Extra) -> {ok, Handle} | {error, Reason}</name> <fsummary>Opens a Telnet connection to the specified target host.</fsummary> <type> @@ -531,7 +531,7 @@ </func> <func> - <name>send(Connection, Cmd) -> ok | {error, Reason}</name> + <name since="">send(Connection, Cmd) -> ok | {error, Reason}</name> <fsummary>Equivalent to send(Connection, Cmd, []).</fsummary> <desc><marker id="send-2"/> <p>Equivalent to @@ -541,7 +541,7 @@ </func> <func> - <name>send(Connection, Cmd, Opts) -> ok | {error, Reason}</name> + <name since="OTP 17.4">send(Connection, Cmd, Opts) -> ok | {error, Reason}</name> <fsummary>Sends a Telnet command and returns immediately.</fsummary> <type> <v>Connection = connection()</v> @@ -566,7 +566,7 @@ </func> <func> - <name>sendf(Connection, CmdFormat, Args) -> ok | {error, Reason}</name> + <name since="">sendf(Connection, CmdFormat, Args) -> ok | {error, Reason}</name> <fsummary>Equivalent to sendf(Connection, CmdFormat, Args, []).</fsummary> <desc><marker id="sendf-3"/> <p>Equivalent to @@ -576,7 +576,7 @@ </func> <func> - <name>sendf(Connection, CmdFormat, Args, Opts) -> ok | {error, Reason}</name> + <name since="OTP 17.4">sendf(Connection, CmdFormat, Args, Opts) -> ok | {error, Reason}</name> <fsummary>Sends a Telnet command and returns immediately (uses a format string and a list of arguments to build the command).</fsummary> <type> diff --git a/lib/common_test/doc/src/ct_testspec.xml b/lib/common_test/doc/src/ct_testspec.xml index 36893f66cf..9cb9a2ae9f 100644 --- a/lib/common_test/doc/src/ct_testspec.xml +++ b/lib/common_test/doc/src/ct_testspec.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ct_testspec.xml</file> </header> - <module>ct_testspec</module> + <module since="OTP 19.3">ct_testspec</module> <modulesummary>Parsing of test specifications for Common Test. </modulesummary> @@ -46,7 +46,7 @@ <funcs> <func> - <name>get_tests(SpecsIn) -> {ok, [{Specs,Tests}]} | {error, Reason}</name> + <name since="OTP 19.3">get_tests(SpecsIn) -> {ok, [{Specs,Tests}]} | {error, Reason}</name> <fsummary>Parse the given test specification files and return the tests to run and skip.</fsummary> <type> <v>SpecsIn = [string()] | [[string()]]</v> diff --git a/lib/common_test/doc/src/unix_telnet.xml b/lib/common_test/doc/src/unix_telnet.xml index b2314a53ec..03d91b7dbe 100644 --- a/lib/common_test/doc/src/unix_telnet.xml +++ b/lib/common_test/doc/src/unix_telnet.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>unix_telnet.xml</file> </header> - <module>unix_telnet</module> + <module since="">unix_telnet</module> <modulesummary>Callback module for ct_telnet, for connecting to a Telnet server on a UNIX host.</modulesummary> @@ -80,7 +80,7 @@ <funcs> <func> - <name>connect(ConnName, Ip, Port, Timeout, KeepAlive, TCPNoDelay, Extra) -> {ok, Handle} | {error, Reason}</name> + <name since="OTP 18.3.3">connect(ConnName, Ip, Port, Timeout, KeepAlive, TCPNoDelay, Extra) -> {ok, Handle} | {error, Reason}</name> <fsummary>Callback for ct_telnet.erl.</fsummary> <type> <v>ConnName = target_name()</v> @@ -107,7 +107,7 @@ </func> <func> - <name>get_prompt_regexp() -> PromptRegexp</name> + <name since="">get_prompt_regexp() -> PromptRegexp</name> <fsummary>Callback for ct_telnet.erl.</fsummary> <type> <v>PromptRegexp = prompt_regexp()</v> diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl index a10d939919..a07e61199b 100644 --- a/lib/common_test/src/ct_config.erl +++ b/lib/common_test/src/ct_config.erl @@ -592,7 +592,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) -> encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) -> _ = crypto:start(), - {Key,IVec} = make_crypto_key(Key), + {CryptoKey,IVec} = make_crypto_key(Key), case file:read_file(SrcFileName) of {ok,Bin0} -> Bin1 = term_to_binary({SrcFileName,Bin0}), @@ -600,7 +600,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) -> 0 -> Bin1; N -> list_to_binary([Bin1,random_bytes(8-N)]) end, - EncBin = crypto:block_encrypt(des3_cbc, Key, IVec, Bin2), + EncBin = crypto:block_encrypt(des3_cbc, CryptoKey, IVec, Bin2), case file:write_file(EncryptFileName, EncBin) of ok -> io:format("~ts --(encrypt)--> ~ts~n", @@ -631,10 +631,10 @@ decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) -> decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) -> _ = crypto:start(), - {Key,IVec} = make_crypto_key(Key), + {CryptoKey,IVec} = make_crypto_key(Key), case file:read_file(EncryptFileName) of {ok,Bin} -> - DecBin = crypto:block_decrypt(des3_cbc, Key, IVec, Bin), + DecBin = crypto:block_decrypt(des3_cbc, CryptoKey, IVec, Bin), case catch binary_to_term(DecBin) of {'EXIT',_} -> {error,bad_file}; diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index 7e98e6395f..506147474f 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -314,8 +314,8 @@ add_defaults(Mod,Func, GroupPath) -> ErrStr = io_lib:format("~n*** ERROR *** " "~w:suite/0 failed: ~tp~n", [Suite,Reason]), - io:format(ErrStr, []), - io:format(?def_gl, ErrStr, []), + io:format("~ts", [ErrStr]), + io:format(?def_gl, "~ts", [ErrStr]), {suite0_failed,{exited,Reason}}; SuiteInfo when is_list(SuiteInfo) -> case lists:all(fun(E) when is_tuple(E) -> true; @@ -337,16 +337,16 @@ add_defaults(Mod,Func, GroupPath) -> "Invalid return value from " "~w:suite/0: ~tp~n", [Suite,SuiteInfo]), - io:format(ErrStr, []), - io:format(?def_gl, ErrStr, []), + io:format("~ts", [ErrStr]), + io:format(?def_gl, "~ts", [ErrStr]), {suite0_failed,bad_return_value} end; SuiteInfo -> ErrStr = io_lib:format("~n*** ERROR *** " "Invalid return value from " "~w:suite/0: ~tp~n", [Suite,SuiteInfo]), - io:format(ErrStr, []), - io:format(?def_gl, ErrStr, []), + io:format("~ts", [ErrStr]), + io:format(?def_gl, "~ts", [ErrStr]), {suite0_failed,bad_return_value} end. @@ -373,8 +373,8 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) -> "Invalid return value from " "~w:group(~tw): ~tp~n", [Mod,GrName,BadGr0Val]), - io:format(Gr0ErrStr, []), - io:format(?def_gl, Gr0ErrStr, []), + io:format("~ts", [Gr0ErrStr]), + io:format(?def_gl, "~ts", [Gr0ErrStr]), {group0_failed,bad_return_value}; _ -> Args = if Func == init_per_group ; Func == end_per_group -> @@ -395,8 +395,8 @@ add_defaults1(Mod,Func, GroupPath, SuiteInfo) -> "Invalid return value from " "~w:~tw/0: ~tp~n", [Mod,Func,BadTC0Val]), - io:format(TC0ErrStr, []), - io:format(?def_gl, TC0ErrStr, []), + io:format("~ts", [TC0ErrStr]), + io:format(?def_gl, "~ts", [TC0ErrStr]), {testcase0_failed,bad_return_value}; _ -> %% let test case info (also for all config funcs) override @@ -972,11 +972,10 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> end, PrintError = fun(ErrorFormat, ErrorArgs) -> - Div = "~n- - - - - - - - - - - - - - - - - - - " - "- - - - - - - - - - - - - - - - - - - - -~n", + Div = "\n- - - - - - - - - - - - - - - - - - - " + "- - - - - - - - - - - - - - - - - - - - -\n", ErrorStr2 = io_lib:format(ErrorFormat, ErrorArgs), - io:format(?def_gl, lists:concat([Div,ErrorStr2,Div,"~n"]), - []), + io:format(?def_gl, "~ts~n", [lists:concat([Div,ErrorStr2,Div])]), Link = "\n\n<a href=\"#end\">" "Full error description and stacktrace" @@ -985,7 +984,8 @@ error_notification(Mod,Func,_Args,{Error,Loc}) -> ct_logs:tc_log(ct_error_notify, ?MAX_IMPORTANCE, "CT Error Notification", - ErrorHtml2++Link, [], []) + "~ts", [ErrorHtml2++Link], + []) end, case Loc of [{?MODULE,error_in_suite}] -> @@ -1181,7 +1181,7 @@ get_all(Mod, ConfTests) -> ErrStr = io_lib:format("~n*** ERROR *** " "~w:all/0 failed: ~tp~n", [Mod,ExitReason]), - io:format(?def_gl, ErrStr, []), + io:format(?def_gl, "~ts", [ErrStr]), %% save the error info so it doesn't get printed twice ct_util:set_testdata_async({{error_in_suite,Mod}, ExitReason}); diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 07a1693d5d..814b80b8bd 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -542,7 +542,7 @@ tc_print(Category,Importance,Format,Args,Opts) -> undefined -> atom_to_list(Category); Hd -> Hd end, - Str = lists:concat([get_header(Heading),Format,"\n\n"]), + Str = lists:flatten([get_header(Heading),Format,"\n\n"]), try io:format(?def_gl, Str, Args) catch @@ -935,7 +935,7 @@ create_io_fun(FromPid, CtLogFd, EscChars) -> {_HdOrFt,S,A} -> {false,S,A}; {S,A} -> {true,S,A} end, - try io_lib:format(Str, Args) of + try io_lib:format(lists:flatten(Str), Args) of IoStr when Escapable, EscChars, IoList == [] -> escape_chars(IoStr); IoStr when Escapable, EscChars -> @@ -1138,10 +1138,10 @@ set_evmgr_gl(GL) -> open_ctlog(MiscIoName) -> {ok,Fd} = file:open(?ct_log_name,[write,{encoding,utf8}]), - io:format(Fd, header("Common Test Framework Log", {[],[1,2],[]}), []), + io:format(Fd, "~ts", [header("Common Test Framework Log", {[],[1,2],[]})]), case file:consult(ct_run:variables_file_name("../")) of {ok,Vars} -> - io:format(Fd, config_table(Vars), []); + io:format(Fd, "~ts", [config_table(Vars)]); {error,Reason} -> {ok,Cwd} = file:get_cwd(), Dir = filename:dirname(Cwd), @@ -1213,7 +1213,7 @@ print_style_error(Fd, IoFormat, StyleSheet, Reason) -> close_ctlog(Fd) -> io:format(Fd, "\n</pre>\n", []), - io:format(Fd, [xhtml("<br><br>\n", "<br /><br />\n") | footer()], []), + io:format(Fd, "~ts", [[xhtml("<br><br>\n", "<br /><br />\n") | footer()]]), ok = file:close(Fd). %%%----------------------------------------------------------------- diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl index fd33ee2280..9fc169789c 100644 --- a/lib/common_test/src/ct_master.erl +++ b/lib/common_test/src/ct_master.erl @@ -350,7 +350,7 @@ master_loop(#state{node_ctrl_pids=[], io_lib:format("~-40.40.*ts~tp\n", [$_,atom_to_list(Node),Result]) end,lists:reverse(Finished)), - log(all,"TEST RESULTS",Str,[]), + log(all,"TEST RESULTS","~ts", [Str]), log(all,"Info","Updating log files",[]), refresh_logs(LogDirs,[]), @@ -574,7 +574,7 @@ refresh_logs([],Refreshed) -> io_lib:format("Refreshing logs in ~tp... ~tp", [D,Result]) end,Refreshed), - log(all,"Info",Str,[]). + log(all,"Info","~ts", [Str]). %%%----------------------------------------------------------------- %%% NODE CONTROLLER, runs and controls tests on a test node. diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl index 8b1c7d47bb..b97c6e59e7 100644 --- a/lib/common_test/src/ct_repeat.erl +++ b/lib/common_test/src/ct_repeat.erl @@ -278,7 +278,7 @@ log_loop_info(Args) -> ForceStop -> io_lib:format("force_stop is set to: ~w",[ForceStop]) end, - ct_logs:log("Test loop info",LogStr1++LogStr2++LogStr3++LogStr4,[]) + ct_logs:log("Test loop info","~ts", [LogStr1++LogStr2++LogStr3++LogStr4]) end. ts(Secs) -> diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl index 86081369b9..fe869a4373 100644 --- a/lib/common_test/src/cth_log_redirect.erl +++ b/lib/common_test/src/cth_log_redirect.erl @@ -194,10 +194,10 @@ handle_call({log, case LogFunc of tc_log -> ct_logs:tc_log(Category, ?STD_IMPORTANCE, - Header, String, [], []); + Header, "~ts", [String], []); tc_log_async -> ct_logs:tc_log_async(sasl, ?STD_IMPORTANCE, - Header, String, []) + Header, "~ts", [String]) end, {reply,ok,State}; @@ -261,34 +261,34 @@ handle_remote_events(Bool) -> format_header(#eh_state{curr_suite = undefined, curr_group = undefined, curr_func = undefined}) -> - io_lib:format("System report", []); + lists:flatten(io_lib:format("System report", [])); format_header(#eh_state{curr_suite = Suite, curr_group = undefined, curr_func = undefined}) -> - io_lib:format("System report during ~w", [Suite]); + lists:flatten(io_lib:format("System report during ~w", [Suite])); format_header(#eh_state{curr_suite = Suite, curr_group = undefined, curr_func = TcOrConf}) -> - io_lib:format("System report during ~w:~tw/1", - [Suite,TcOrConf]); + lists:flatten(io_lib:format("System report during ~w:~tw/1", + [Suite,TcOrConf])); format_header(#eh_state{curr_suite = Suite, curr_group = Group, curr_func = Conf}) when Conf == init_per_group; Conf == end_per_group -> - io_lib:format("System report during ~w:~w/2 for ~tw", - [Suite,Conf,Group]); + lists:flatten(io_lib:format("System report during ~w:~w/2 for ~tw", + [Suite,Conf,Group])); format_header(#eh_state{curr_suite = Suite, curr_group = Group, parallel_tcs = true}) -> - io_lib:format("System report during ~tw in ~w", - [Group,Suite]); + lists:flatten(io_lib:format("System report during ~tw in ~w", + [Group,Suite])); format_header(#eh_state{curr_suite = Suite, curr_group = Group, curr_func = TC}) -> - io_lib:format("System report during ~w:~tw/1 in ~tw", - [Suite,TC,Group]). + lists:flatten(io_lib:format("System report during ~w:~tw/1 in ~tw", + [Suite,TC,Group])). diff --git a/lib/common_test/src/cth_surefire.erl b/lib/common_test/src/cth_surefire.erl index b0742717ae..c9b4cb10c6 100644 --- a/lib/common_test/src/cth_surefire.erl +++ b/lib/common_test/src/cth_surefire.erl @@ -235,7 +235,7 @@ close_suite(#state{ test_cases = TCs, url_base = UrlBase } = State) -> terminate(State = #state{ test_cases = [] }) -> {ok,D} = file:open(State#state.filepath,[write,{encoding,utf8}]), io:format(D, "<?xml version=\"1.0\" encoding= \"UTF-8\" ?>", []), - io:format(D, to_xml(State), []), + io:format(D, "~ts", [to_xml(State)]), catch file:sync(D), catch file:close(D); terminate(State) -> diff --git a/lib/common_test/src/test_server_ctrl.erl b/lib/common_test/src/test_server_ctrl.erl index 34f2feb33c..1518c6e8d6 100644 --- a/lib/common_test/src/test_server_ctrl.erl +++ b/lib/common_test/src/test_server_ctrl.erl @@ -1558,7 +1558,7 @@ do_test_cases(TopCases, SkipCases, Html1} end, - print(html, Header), + print(html, "~ts", [Header]), print(html, xhtml("<p>", "<h4>")), print_timestamp(html, "Test started at "), @@ -1605,10 +1605,10 @@ do_test_cases(TopCases, SkipCases, [?suitelog_name,CoverLog,?unexpected_io_log]), print(html, "<p>~ts</p>\n" ++ - xhtml(["<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n", - "<thead>\n"], - ["<table id=\"",?sortable_table_name,"\">\n", - "<thead>\n"]) ++ + xhtml("<table bgcolor=\"white\" border=\"3\" cellpadding=\"5\">\n" ++ + "<thead>\n", + "<table id=\"" ++ ?sortable_table_name ++ "\">\n" ++ + "<thead>\n") ++ "<tr><th>Num</th><th>Module</th><th>Group</th>" ++ "<th>Case</th><th>Log</th><th>Time</th><th>Result</th>" ++ "<th>Comment</th></tr>\n</thead>\n<tbody>\n", @@ -3306,7 +3306,8 @@ skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) -> true -> print(2,"*** Skipping test case #~w ~tw ***", [CaseNum,{Mod,Func}]) end, - TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]), + TR = xhtml("<tr valign=\"top\">", + "<tr class=\"" ++ odd_or_even() ++ "\">"), GroupName = case get_name(Mode) of undefined -> ""; Name -> cast_to_list(Name) @@ -3796,8 +3797,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, end, print(minor, - escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print])), - []), + "~ts", + [escape_chars(io_lib:format("Config value:\n\n ~tp\n", [Args2Print]))]), print(minor, "Current directory is ~tp\n", [Cwd]), GrNameStr = case GrName of @@ -3806,7 +3807,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, end, print(major, "=started ~s", [lists:flatten(timestamp_get(""))]), {{Col0,Col1},Style} = get_font_style((RunInit==run_init), Mode), - TR = xhtml("<tr valign=\"top\">", ["<tr class=\"",odd_or_even(),"\">"]), + TR = xhtml("<tr valign=\"top\">", "<tr class=\"" ++ odd_or_even() ++ "\">"), EncMinorBase = uri_encode(MinorBase), print(html, TR ++ "<td>" ++ Col0 ++ "~ts" ++ Col1 ++ "</td>" "<td>" ++ Col0 ++ "~w" ++ Col1 ++ "</td>" @@ -3831,7 +3832,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, print(minor, "<a name=\"end\"></a>", [], internal_raw), print(minor, "\n", [], internal_raw), print_timestamp(minor, "Ended at "), - print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]), + print(major, "=ended ~s", [timestamp_get("")]), do_unless_parallel(Main, fun() -> file:set_cwd(filename:dirname(TSDir)) end), @@ -4075,9 +4076,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, {testcase_aborted,Reason}, _T, FormatLoc = test_server_sup:format_loc(Loc), print(minor, "=== Location: ~ts", [FormatLoc]), print(minor, - escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}", - [Reason])), - []), + "~ts", + [escape_chars(io_lib:format("=== Reason: {testcase_aborted,~tp}", + [Reason]))]), failed; progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time, @@ -4115,8 +4116,8 @@ progress(failed, CaseNum, Mod, Func, GrName, unknown, Reason, Time, print(minor, "=== Location: ~w", [unknown]), {FStr,FormattedReason} = format_exception(Reason), print(minor, - escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason])), - []), + "~ts", + [escape_chars(io_lib:format("=== Reason: " ++ FStr, [FormattedReason]))]), failed; progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time, @@ -4150,8 +4151,9 @@ progress(failed, CaseNum, Mod, Func, GrName, Loc, Reason, Time, FormatLoc = test_server_sup:format_loc(LocMin), print(minor, "=== Location: ~ts", [FormatLoc]), {FStr,FormattedReason} = format_exception(Reason), - print(minor, "=== Reason: " ++ - escape_chars(io_lib:format(FStr, [FormattedReason])), []), + print(minor, "~ts", + ["=== Reason: " ++ + escape_chars(io_lib:format(FStr, [FormattedReason]))]), failed; progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time, @@ -4184,8 +4186,8 @@ progress(ok, _CaseNum, Mod, Func, GrName, _Loc, RetVal, Time, "~ts</tr>\n", [TimeStr,Comment]), print(minor, - escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal])), - []), + "~ts", + [escape_chars(io_lib:format("=== Returned value: ~tp", [RetVal]))]), ok. %%-------------------------------------------------------------------- @@ -4542,7 +4544,7 @@ timestamp_get(Leader) -> timestamp_get_internal(Leader, Format) -> {YY,MM,DD,H,M,S} = time_get(), - io_lib:format(Format, [Leader,YY,MM,DD,H,M,S]). + lists:flatten(io_lib:format(Format, [Leader,YY,MM,DD,H,M,S])). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% time_get() -> {YY,MM,DD,H,M,S} diff --git a/lib/common_test/test_server/ts_benchmark.erl b/lib/common_test/test_server/ts_benchmark.erl index e4e06b54c2..a9486262b3 100644 --- a/lib/common_test/test_server/ts_benchmark.erl +++ b/lib/common_test/test_server/ts_benchmark.erl @@ -45,7 +45,7 @@ run(Specs, Opts, Vars) -> || Spec <- Specs], file:delete(filename:join(Cwd,"latest_benchmark")), {ok,D} = file:open(filename:join(Cwd,"latest_benchmark"),[write]), - io:format(D,BDir,[]), + io:format(D,"~ts", [BDir]), file:close(D). diff --git a/lib/common_test/test_server/ts_erl_config.erl b/lib/common_test/test_server/ts_erl_config.erl index 537628e39a..f3972bea4e 100644 --- a/lib/common_test/test_server/ts_erl_config.erl +++ b/lib/common_test/test_server/ts_erl_config.erl @@ -208,7 +208,11 @@ erl_interface(Vars,OsType) -> {filename:join(Dir, "lib"), filename:join([Dir, "src", "eidefs.mk"])}; {srctree, _Root, Target} -> - {filename:join([Dir, "obj", Target]), + Obj = case is_debug_build() of + true -> "obj.debug"; + false -> "obj" + end, + {filename:join([Dir, Obj, Target]), filename:join([Dir, "src", Target, "eidefs.mk"])} end} end, diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index 45e442f5c2..5219ba0f5d 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -29,7 +29,7 @@ <rev>A</rev> <file>compile.sgml</file> </header> - <module>compile</module> + <module since="">compile</module> <modulesummary>Erlang Compiler</modulesummary> <description> <p>This module provides an interface to the standard Erlang @@ -40,7 +40,7 @@ <funcs> <func> - <name>env_compiler_options()</name> + <name since="OTP 19.0">env_compiler_options()</name> <fsummary> Compiler options defined via the environment variable <c>ERL_COMPILER_OPTIONS</c> @@ -53,7 +53,7 @@ </desc> </func> <func> - <name>file(File)</name> + <name since="">file(File)</name> <fsummary>Compiles a file.</fsummary> <desc> <p>Is the same as @@ -63,7 +63,7 @@ </func> <func> - <name>file(File, Options) -> CompRet</name> + <name since="">file(File, Options) -> CompRet</name> <fsummary>Compiles a file.</fsummary> <type> <v>CompRet = ModRet | BinRet | ErrRet</v> @@ -695,12 +695,13 @@ module.beam: module.erl \ </note> <note> - <p>The options <c>{nowarn_unused_function, FAs}</c>, - <c>{nowarn_bif_clash, FAs}</c>, and - <c>{nowarn_deprecated_function, MFAs}</c> are only - recognized when given in files. They are not affected by - options <c>warn_unused_function</c>, <c>warn_bif_clash</c>, or - <c>warn_deprecated_function</c>.</p> + <p>Before OTP 22, the option <c>{nowarn_deprecated_function, + MFAs}</c> was only recognized when given in the file with + attribute <c>-compile()</c>. (The option + <c>{nowarn_unused_function,FAs}</c> was incorrectly documented + to only work in a file, but it also worked when given in the + option list.) Starting from OTP 22, all options that can be + given in the file can also be given in the option list.</p> </note> <p>For debugging of the compiler, or for pure curiosity, @@ -729,7 +730,7 @@ module.beam: module.erl \ </func> <func> - <name>forms(Forms)</name> + <name since="">forms(Forms)</name> <fsummary>Compiles a list of forms.</fsummary> <desc> <p>Is the same as @@ -739,7 +740,7 @@ module.beam: module.erl \ </func> <func> - <name>forms(Forms, Options) -> CompRet</name> + <name since="">forms(Forms, Options) -> CompRet</name> <fsummary>Compiles a list of forms.</fsummary> <type> <v>Forms = [Form]</v> @@ -760,7 +761,7 @@ module.beam: module.erl \ </func> <func> - <name>format_error(ErrorDescriptor) -> chars()</name> + <name since="">format_error(ErrorDescriptor) -> chars()</name> <fsummary>Formats an error descriptor.</fsummary> <type> <v>ErrorDescriptor = errordesc()</v> @@ -775,7 +776,7 @@ module.beam: module.erl \ </func> <func> - <name>output_generated(Options) -> true | false</name> + <name since="">output_generated(Options) -> true | false</name> <fsummary>Determines whether the compiler generates an output file.</fsummary> <type> <v>Options = [term()]</v> @@ -790,7 +791,7 @@ module.beam: module.erl \ </func> <func> - <name>noenv_file(File, Options) -> CompRet</name> + <name since="">noenv_file(File, Options) -> CompRet</name> <fsummary>Compiles a file (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary> <desc> <p>Works like <seealso marker="#file/2">file/2</seealso>, @@ -800,7 +801,7 @@ module.beam: module.erl \ </func> <func> - <name>noenv_forms(Forms, Options) -> CompRet</name> + <name since="">noenv_forms(Forms, Options) -> CompRet</name> <fsummary>Compiles a list of forms (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary> <desc> <p>Works like <seealso marker="#forms/2">forms/2</seealso>, @@ -810,7 +811,7 @@ module.beam: module.erl \ </func> <func> - <name>noenv_output_generated(Options) -> true | false</name> + <name since="">noenv_output_generated(Options) -> true | false</name> <fsummary>Determines whether the compiler generates an output file (ignoring <c>ERL_COMPILER_OPTIONS)</c>.</fsummary> <type> diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index e0e5bc832b..02e6203137 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,81 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>An optimization that avoided allocation of a stack + frame for some <c>case</c> expressions was introduced in + OTP 21. (ERL-504/OTP-14808) It turns out that in rare + circumstances, this optimization is not safe. Therefore, + this optimization has been disabled.</p> + <p>A similar optimization will be included in OTP 22 in a + safe way.</p> + <p> + Own Id: OTP-15501 Aux Id: ERL-807, ERL-514, OTP-14808 </p> + </item> + </list> + </section> + +</section> + +<section><title>Compiler 7.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fixed a rare internal consistency failure caused by a + bug in the <c>beam_jump</c> pass. (Thanks to Simon + Cornish for reporting this bug.)</p> + <p> + Own Id: OTP-15400 Aux Id: ERL-759 </p> + </item> + <item> + <p>The compiler could fail with an internal consistency + check failure when compiling code that used the + <c>is_function/2</c> BIF.</p> + <p> + Own Id: OTP-15435 Aux Id: ERL-778 </p> + </item> + <item> + <p>When an external fun was used, warnings for unused + variables could be suppressed.</p> + <p> + Own Id: OTP-15437 Aux Id: ERL-762 </p> + </item> + <item> + <p>The compiler would crash when compiling an + <c>after</c> block that called <c>erlang:raise/3</c> like + this: <c>erlang:raise(Class, Stacktrace, + Stacktrace)</c></p> + <p> + Own Id: OTP-15481</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>When specified, the <c>+{source,Name}</c> option will + now override the actual file name in stack traces, + instead of only affecting the return value of + <c>Mod:module_info()</c>.</p> + <p>The <c>+deterministic</c> flag will also affect stack + traces now, omitting all path information except the file + name, fixing a long-standing issue where deterministic + builds required deterministic paths.</p> + <p> + Own Id: OTP-15245 Aux Id: ERL-706 </p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.2.7</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile index d475e5a19a..97c73d0e07 100644 --- a/lib/compiler/src/Makefile +++ b/lib/compiler/src/Makefile @@ -49,7 +49,6 @@ MODULES = \ beam_a \ beam_asm \ beam_block \ - beam_bs \ beam_clean \ beam_dict \ beam_disasm \ @@ -69,6 +68,7 @@ MODULES = \ beam_ssa_pp \ beam_ssa_pre_codegen \ beam_ssa_recv \ + beam_ssa_share \ beam_ssa_type \ beam_kernel_to_ssa \ beam_trim \ @@ -103,6 +103,7 @@ BEAM_H = $(wildcard ../priv/beam_h/*.h) HRL_FILES= \ beam_disasm.hrl \ + beam_ssa_opt.hrl \ beam_ssa.hrl \ core_parse.hrl \ v3_kernel.hrl diff --git a/lib/compiler/src/beam_a.erl b/lib/compiler/src/beam_a.erl index dd2537a699..1ac892a8f1 100644 --- a/lib/compiler/src/beam_a.erl +++ b/lib/compiler/src/beam_a.erl @@ -100,8 +100,12 @@ rename_instr({bs_put_utf16=I,F,Fl,Src}) -> {bs_put,F,{I,Fl},[Src]}; rename_instr({bs_put_utf32=I,F,Fl,Src}) -> {bs_put,F,{I,Fl},[Src]}; -rename_instr({bs_put_string,_,_}=I) -> - {bs_put,{f,0},I,[]}; +rename_instr({bs_put_string,_,{string,String}}) -> + %% Only happens when compiling from .S files. In old + %% .S files, String is a list. In .S in OTP 22 and later, + %% String is a binary. + {bs_put,{f,0},{bs_put_binary,8,{field_flags,[unsigned,big]}}, + [{atom,all},{literal,iolist_to_binary([String])}]}; rename_instr({bs_add=I,F,[Src1,Src2,U],Dst}) when is_integer(U) -> {bif,I,F,[Src1,Src2,{integer,U}],Dst}; rename_instr({bs_utf8_size=I,F,Src,Dst}) -> @@ -118,8 +122,8 @@ rename_instr({bs_private_append=I,F,Sz,U,Src,Flags,Dst}) -> {bs_init,F,{I,U,Flags},none,[Sz,Src],Dst}; rename_instr(bs_init_writable=I) -> {bs_init,{f,0},I,1,[{x,0}],{x,0}}; -rename_instr({test,Op,F,[Ctx,Bits,{string,Str}]}) -> - %% When compiling from a .S file. +rename_instr({test,bs_match_string=Op,F,[Ctx,Bits,{string,Str}]}) when is_list(Str) -> + %% When compiling from an old .S file. Starting from OTP 22, Str is a binary. <<Bs:Bits/bits,_/bits>> = list_to_binary(Str), {test,Op,F,[Ctx,Bs]}; rename_instr({put_map_assoc,Fail,S,D,R,L}) -> diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index df0321e85a..bc1290f6fd 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -424,8 +424,8 @@ encode_arg({f, W}, Dict) -> {encode(?tag_f, W), Dict}; %% encode_arg({'char', C}, Dict) -> %% {encode(?tag_h, C), Dict}; -encode_arg({string, String}, Dict0) -> - {Offset, Dict} = beam_dict:string(String, Dict0), +encode_arg({string, BinString}, Dict0) when is_binary(BinString) -> + {Offset, Dict} = beam_dict:string(BinString, Dict0), {encode(?tag_u, Offset), Dict}; encode_arg({extfunc, M, F, A}, Dict0) -> {Index, Dict} = beam_dict:import(M, F, A, Dict0), diff --git a/lib/compiler/src/beam_bs.erl b/lib/compiler/src/beam_bs.erl deleted file mode 100644 index 15d8d687fc..0000000000 --- a/lib/compiler/src/beam_bs.erl +++ /dev/null @@ -1,183 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-2018. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% 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% -%% -%% Purpose: Peephole optimization of binary syntax instructions. - --module(beam_bs). - --export([module/2]). --import(lists, [reverse/1]). - --spec module(beam_utils:module_code(), [compile:option()]) -> - {'ok',beam_utils:module_code()}. - -module({Mod,Exp,Attr,Fs0,Lc}, _Opt) -> - Fs = [function(F) || F <- Fs0], - {ok,{Mod,Exp,Attr,Fs,Lc}}. - -function({function,Name,Arity,CLabel,Is0}) -> - try - Is = bs_opt(Is0), - {function,Name,Arity,CLabel,Is} - catch - Class:Error:Stack -> - io:fwrite("Function: ~w/~w\n", [Name,Arity]), - erlang:raise(Class, Error, Stack) - end. - -%%% -%%% Evaluate construction of constant bit fields. -%%% Combine bs_skip_bits2 and bs_test_tail2 instructions. -%%% - -bs_opt([{bs_put,_,_,_}=I|Is0]) -> - {BsPuts0,Is} = collect_bs_puts(Is0, [I]), - BsPuts = opt_bs_puts(BsPuts0), - BsPuts ++ bs_opt(Is); -bs_opt([{test,bs_skip_bits2,F,[Ctx,{integer,I},Unit,_Flags]}, - {test,bs_test_tail2,F,[Ctx,Bits]}|Is]) -> - [{test,bs_test_tail2,F,[Ctx,Bits+I*Unit]}|bs_opt(Is)]; -bs_opt([{test,bs_skip_bits2,F,[Ctx,{integer,I1},Unit1,Flags]}, - {test,bs_skip_bits2,F,[Ctx,{integer,I2},Unit2,_]}|Is]) -> - I = {test,bs_skip_bits2,F, - [Ctx,{integer,I1*Unit1+I2*Unit2},1,Flags]}, - bs_opt([I|Is]); -bs_opt([I|Is]) -> - [I|bs_opt(Is)]; -bs_opt([]) -> []. - -collect_bs_puts([{bs_put,_,_,_}=I|Is], Acc) -> - collect_bs_puts(Is, [I|Acc]); -collect_bs_puts([_|_]=Is, Acc) -> - {reverse(Acc),Is}. - -opt_bs_puts(Is) -> - opt_bs_1(Is, []). - -opt_bs_1([{bs_put,Fail, - {bs_put_float,1,Flags0},[{integer,Sz},Src]}=I0|Is], Acc) -> - try eval_put_float(Src, Sz, Flags0) of - <<Int:Sz>> -> - Flags = force_big(Flags0), - I = {bs_put,Fail,{bs_put_integer,1,Flags}, - [{integer,Sz},{integer,Int}]}, - opt_bs_1([I|Is], Acc) - catch - error:_ -> - opt_bs_1(Is, [I0|Acc]) - end; -opt_bs_1([{bs_put,_,{bs_put_integer,1,_},[{integer,8},{integer,_}]}|_]=IsAll, - Acc0) -> - {Is,Acc} = bs_collect_string(IsAll, Acc0), - opt_bs_1(Is, Acc); -opt_bs_1([{bs_put,Fail,{bs_put_integer,1,F},[{integer,Sz},{integer,N}]}=I|Is0], - Acc) when Sz > 8 -> - case field_endian(F) of - big -> - %% We can do this optimization for any field size without - %% risk for code explosion. - case bs_split_int(N, Sz, Fail, Is0) of - no_split -> opt_bs_1(Is0, [I|Acc]); - Is -> opt_bs_1(Is, Acc) - end; - little when Sz < 128 -> - %% We only try to optimize relatively small fields, to - %% avoid an explosion in code size. - <<Int:Sz>> = <<N:Sz/little>>, - Flags = force_big(F), - Is = [{bs_put,Fail,{bs_put_integer,1,Flags}, - [{integer,Sz},{integer,Int}]}|Is0], - opt_bs_1(Is, Acc); - _ -> %native or too wide little field - opt_bs_1(Is0, [I|Acc]) - end; -opt_bs_1([{bs_put,Fail,{Op,U,F},[{integer,Sz},Src]}|Is], Acc) when U > 1 -> - opt_bs_1([{bs_put,Fail,{Op,1,F},[{integer,U*Sz},Src]}|Is], Acc); -opt_bs_1([I|Is], Acc) -> - opt_bs_1(Is, [I|Acc]); -opt_bs_1([], Acc) -> reverse(Acc). - -eval_put_float(Src, Sz, Flags) when Sz =< 256 -> - %%Only evaluate if Sz is reasonable. - Val = value(Src), - case field_endian(Flags) of - little -> <<Val:Sz/little-float-unit:1>>; - big -> <<Val:Sz/big-float-unit:1>> - %% native intentionally not handled here - we can't optimize - %% it. - end. - -value({integer,I}) -> I; -value({float,F}) -> F. - -bs_collect_string(Is, [{bs_put,_,{bs_put_string,Len,{string,Str}},[]}|Acc]) -> - bs_coll_str_1(Is, Len, reverse(Str), Acc); -bs_collect_string(Is, Acc) -> - bs_coll_str_1(Is, 0, [], Acc). - -bs_coll_str_1([{bs_put,_,{bs_put_integer,U,_},[{integer,Sz},{integer,V}]}|Is], - Len, StrAcc, IsAcc) when U*Sz =:= 8 -> - Byte = V band 16#FF, - bs_coll_str_1(Is, Len+1, [Byte|StrAcc], IsAcc); -bs_coll_str_1(Is, Len, StrAcc, IsAcc) -> - {Is,[{bs_put,{f,0},{bs_put_string,Len,{string,reverse(StrAcc)}},[]}|IsAcc]}. - -field_endian({field_flags,F}) -> field_endian_1(F). - -field_endian_1([big=E|_]) -> E; -field_endian_1([little=E|_]) -> E; -field_endian_1([native=E|_]) -> E; -field_endian_1([_|Fs]) -> field_endian_1(Fs). - -force_big({field_flags,F}) -> - {field_flags,force_big_1(F)}. - -force_big_1([big|_]=Fs) -> Fs; -force_big_1([little|Fs]) -> [big|Fs]; -force_big_1([F|Fs]) -> [F|force_big_1(Fs)]. - -bs_split_int(0, Sz, _, _) when Sz > 64 -> - %% We don't want to split in this case because the - %% string will consist of only zeroes. - no_split; -bs_split_int(-1, Sz, _, _) when Sz > 64 -> - %% We don't want to split in this case because the - %% string will consist of only 255 bytes. - no_split; -bs_split_int(N, Sz, Fail, Acc) -> - FirstByteSz = case Sz rem 8 of - 0 -> 8; - Rem -> Rem - end, - bs_split_int_1(N, FirstByteSz, Sz, Fail, Acc). - -bs_split_int_1(-1, _, Sz, Fail, Acc) when Sz > 64 -> - I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}}, - [{integer,Sz},{integer,-1}]}, - [I|Acc]; -bs_split_int_1(0, _, Sz, Fail, Acc) when Sz > 64 -> - I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}}, - [{integer,Sz},{integer,0}]}, - [I|Acc]; -bs_split_int_1(N, ByteSz, Sz, Fail, Acc) when Sz > 0 -> - Mask = (1 bsl ByteSz) - 1, - I = {bs_put,Fail,{bs_put_integer,1,{field_flags,[big]}}, - [{integer,ByteSz},{integer,N band Mask}]}, - bs_split_int_1(N bsr ByteSz, 8, Sz-ByteSz, Fail, [I|Acc]); -bs_split_int_1(_, _, _, _, Acc) -> Acc. diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl index 990e86062a..b2056332e6 100644 --- a/lib/compiler/src/beam_dict.erl +++ b/lib/compiler/src/beam_dict.erl @@ -126,18 +126,17 @@ import(Mod0, Name0, Arity, #asm{imports=Imp0,next_import=NextIndex}=D0) {NextIndex,D2#asm{imports=Imp,next_import=NextIndex+1}} end. -%% Returns the index for a string in the string table (adding the string to the -%% table if necessary). +%% Returns the index for a binary string in the string table (adding +%% the string to the table if necessary). %% string(String, Dict) -> {Offset, Dict'} --spec string(string(), bdict()) -> {non_neg_integer(), bdict()}. +-spec string(binary(), bdict()) -> {non_neg_integer(), bdict()}. -string(Str, Dict) when is_list(Str) -> +string(BinString, Dict) when is_binary(BinString) -> #asm{strings=Strings,string_offset=NextOffset} = Dict, - StrBin = list_to_binary(Str), - case old_string(StrBin, Strings) of + case old_string(BinString, Strings) of none -> - NewDict = Dict#asm{strings = <<Strings/binary,StrBin/binary>>, - string_offset=NextOffset+byte_size(StrBin)}, + NewDict = Dict#asm{strings = <<Strings/binary,BinString/binary>>, + string_offset=NextOffset+byte_size(BinString)}, {NextOffset,NewDict}; Offset when is_integer(Offset) -> {NextOffset-Offset,Dict} diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index d3a618d211..8b0e3e32f8 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -101,6 +101,10 @@ %%% always keep the label. (beam_clean will remove any unused %%% labels.) %%% +%%% (7) Replace a jump to a return instruction with a return instruction. +%%% Similarly, replace a jump to deallocate + return with those +%%% instructions. +%%% %%% Note: This modules depends on (almost) all branches and jumps only %%% going forward, so that we can remove instructions (including definition %%% of labels) after any label that has not been referenced by the code @@ -150,7 +154,8 @@ function({function,Name,Arity,CLabel,Asm0}, Lc0) -> Asm3 = share(Asm2), Asm4 = move(Asm3), Asm5 = opt(Asm4, CLabel), - Asm = remove_unused_labels(Asm5), + Asm6 = unshare(Asm5), + Asm = remove_unused_labels(Asm6), {{function,Name,Arity,CLabel,Asm},Lc} catch Class:Error:Stack -> @@ -393,14 +398,13 @@ extract_seq_1(_, _) -> no. { entry :: beam_asm:label(), %Entry label (must not be moved). replace :: #{beam_asm:label() := beam_asm:label()}, %Labels to replace. - labels :: cerl_sets:set(), %Set of referenced labels. - index :: beam_utils:code_index() | {lazy,[beam_utils:instruction()]} %Index built lazily only if needed + labels :: cerl_sets:set() %Set of referenced labels. }). opt(Is0, CLabel) -> find_fixpoint(fun(Is) -> Lbls = initial_labels(Is), - St = #st{entry=CLabel,replace=#{},labels=Lbls,index={lazy,Is}}, + St = #st{entry=CLabel,replace=#{},labels=Lbls}, opt(Is, [], St) end, Is0). @@ -425,16 +429,6 @@ opt([{test,_,{f,L}=Lbl,_}=I|[{jump,{f,L}}|_]=Is], Acc, St) -> %% as in the jump that follows -- thus it is not needed. opt(Is, Acc, St) end; -opt([{test,_,{f,L}=Lbl,_}=I|[{label,L}|_]=Is], Acc, St) -> - %% Similar to the above, except we have a fall-through rather than jump - %% Test Label Ops - %% label Label - case beam_utils:is_pure_test(I) of - false -> - opt(Is, [I|Acc], label_used(Lbl, St)); - true -> - opt(Is, Acc, St) - end; opt([{test,Test0,{f,L}=Lbl,Ops}=I|[{jump,To}|Is]=Is0], Acc, St) -> case is_label_defined(Is, L) of false -> @@ -625,6 +619,39 @@ drop_upto_label([{label,_}|_]=Is) -> Is; drop_upto_label([_|Is]) -> drop_upto_label(Is); drop_upto_label([]) -> []. +%% unshare([Instruction]) -> [Instruction]. +%% Replace a jump to a return sequence (a `return` instruction +%% optionally preced by a `deallocate` instruction) with the return +%% sequence. This always saves execution time and may also save code +%% space (depending on the architecture). Eliminating `jump` +%% instructions also gives beam_trim more opportunities to trim the +%% stack. + +unshare(Is) -> + Short = unshare_collect_short(Is, #{}), + unshare_short(Is, Short). + +unshare_collect_short([{label,L},return|Is], Map) -> + unshare_collect_short(Is, Map#{L=>[return]}); +unshare_collect_short([{label,L},{deallocate,_}=D,return|Is], Map) -> + %% `deallocate` and `return` are combined into one instruction by + %% the loader. + unshare_collect_short(Is, Map#{L=>[D,return]}); +unshare_collect_short([_|Is], Map) -> + unshare_collect_short(Is, Map); +unshare_collect_short([], Map) -> Map. + +unshare_short([{jump,{f,F}}=I|Is], Map) -> + case Map of + #{F:=Seq} -> + Seq ++ unshare_short(Is, Map); + #{} -> + [I|unshare_short(Is, Map)] + end; +unshare_short([I|Is], Map) -> + [I|unshare_short(Is, Map)]; +unshare_short([], _Map) -> []. + %% ulbl(Instruction, UsedCerlSet) -> UsedCerlSet' %% Update the cerl_set UsedCerlSet with any function-local labels %% (i.e. not with labels in call instructions) referenced by diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl index 2323a439e9..5730e9704e 100644 --- a/lib/compiler/src/beam_peep.erl +++ b/lib/compiler/src/beam_peep.erl @@ -94,30 +94,26 @@ peep([{gc_bif,_,_,_,_,Dst}=I|Is], SeenTests0, Acc) -> peep([{jump,{f,L}},{label,L}=I|Is], _, Acc) -> %% Sometimes beam_jump has missed this optimization. peep(Is, gb_sets:empty(), [I|Acc]); -peep([{select,Op,R,F,Vls0}|Is], SeenTests0, Acc0) -> +peep([{select,select_val,R,F,Vls0}|Is], SeenTests0, Acc0) -> case prune_redundant_values(Vls0, F) of [] -> %% No values left. Must convert to plain jump. I = {jump,F}, peep([I|Is], gb_sets:empty(), Acc0); - [{atom,_}=Value,Lbl] when Op =:= select_val -> + [{atom,_}=Value,Lbl] -> %% Single value left. Convert to regular test. Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is], peep(Is1, SeenTests0, Acc0); - [{integer,_}=Value,Lbl] when Op =:= select_val -> + [{integer,_}=Value,Lbl] -> %% Single value left. Convert to regular test. Is1 = [{test,is_eq_exact,F,[R,Value]},{jump,Lbl}|Is], peep(Is1, SeenTests0, Acc0); - [Arity,Lbl] when Op =:= select_tuple_arity -> - %% Single value left. Convert to regular test - Is1 = [{test,test_arity,F,[R,Arity]},{jump,Lbl}|Is], - peep(Is1, SeenTests0, Acc0); [{atom,B1},Lbl,{atom,B2},Lbl] when B1 =:= not B2 -> %% Replace with is_boolean test. Is1 = [{test,is_boolean,F,[R]},{jump,Lbl}|Is], peep(Is1, SeenTests0, Acc0); [_|_]=Vls -> - I = {select,Op,R,F,Vls}, + I = {select,select_val,R,F,Vls}, peep(Is, gb_sets:empty(), [I|Acc0]) end; peep([{get_map_elements,Fail,Src,List}=I|Is], _SeenTests, Acc0) -> diff --git a/lib/compiler/src/beam_ssa_bsm.erl b/lib/compiler/src/beam_ssa_bsm.erl index 9631bf3334..466337db0e 100644 --- a/lib/compiler/src/beam_ssa_bsm.erl +++ b/lib/compiler/src/beam_ssa_bsm.erl @@ -877,7 +877,8 @@ annotate_context_parameters(F, ModInfo) -> %% Assertion. error(conflicting_parameter_types); (K, suitable_for_reuse, Acc) -> - Acc#{ K => match_context }; + T = beam_validator:type_anno(match_context), + Acc#{ K => T }; (_K, _V, Acc) -> Acc end, TypeAnno0, ParamInfo), diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl index d3facc5911..c2d5035b19 100644 --- a/lib/compiler/src/beam_ssa_codegen.erl +++ b/lib/compiler/src/beam_ssa_codegen.erl @@ -161,7 +161,7 @@ add_parameter_annos([{label, _}=Entry | Body], Anno) -> (_K, _V, Acc) -> Acc end, [], maps:get(registers, Anno)), - [Entry | Annos] ++ Body. + [Entry | sort(Annos)] ++ Body. cg_fun(Blocks, St0) -> Linear0 = linearize(Blocks), @@ -1071,8 +1071,8 @@ cg_block([#cg_set{op={bif,Name},dst=Dst0,args=Args0}]=Is0, {Dst0,Fail}, St0) -> {z,_} -> %% The result of the BIF call will only be used once. Convert to %% a test instruction. - Test = bif_to_test(Name, Args, ensure_label(Fail, St0)), - {Test,St0}; + {Test,St1} = bif_to_test(Name, Args, ensure_label(Fail, St0), St0), + {Test,St1}; _ -> %% Must explicitly call the BIF since the result will be used %% more than once. @@ -1269,16 +1269,17 @@ cg_copy_1([], _St) -> []. element(1, Val) =:= atom orelse element(1, Val) =:= literal)). +bif_to_test('or', [V1,V2], {f,Lbl}=Fail, St0) when Lbl =/= 0 -> + {SuccLabel,St} = new_label(St0), + {[{test,is_eq_exact,{f,SuccLabel},[V1,{atom,false}]}, + {test,is_eq_exact,Fail,[V2,{atom,true}]}, + {label,SuccLabel}],St}; +bif_to_test(Op, Args, Fail, St) -> + {bif_to_test(Op, Args, Fail),St}. + bif_to_test('and', [V1,V2], Fail) -> [{test,is_eq_exact,Fail,[V1,{atom,true}]}, {test,is_eq_exact,Fail,[V2,{atom,true}]}]; -bif_to_test('or', [V1,V2], {f,Lbl}=Fail) when Lbl =/= 0 -> - %% Labels are spaced 2 apart. We can create a new - %% label by incrementing the Fail label. - SuccLabel = Lbl + 1, - [{test,is_eq_exact,{f,SuccLabel},[V1,{atom,false}]}, - {test,is_eq_exact,Fail,[V2,{atom,true}]}, - {label,SuccLabel}]; bif_to_test('not', [Var], Fail) -> [{test,is_eq_exact,Fail,[Var,{atom,false}]}]; bif_to_test(Name, Args, Fail) -> @@ -1448,7 +1449,12 @@ cg_call(#cg_set{anno=Anno,op=call,dst=Dst0,args=[#b_local{}=Func0|Args0]}, Line = call_line(Where, local, Anno), Call = build_call(call, Arity, {f,FuncLbl}, Context, Dst), Is = setup_args(Args, Anno, Context, St) ++ Line ++ Call, - {Is,St}; + case Anno of + #{ result_type := Info } -> + {Is ++ [{'%', {type_info, Dst, Info}}], St}; + #{} -> + {Is, St} + end; cg_call(#cg_set{anno=Anno0,op=call,dst=Dst0,args=[#b_remote{}=Func0|Args0]}, Where, Context, St) -> [Dst|Args] = beam_args([Dst0|Args0], St), @@ -1724,6 +1730,14 @@ copy(Src, Dst) -> [{move,Src,Dst}]. force_reg({literal,_}=Lit, Reg) -> {Reg,[{move,Lit,Reg}]}; +force_reg({integer,_}=Lit, Reg) -> + {Reg,[{move,Lit,Reg}]}; +force_reg({atom,_}=Lit, Reg) -> + {Reg,[{move,Lit,Reg}]}; +force_reg({float,_}=Lit, Reg) -> + {Reg,[{move,Lit,Reg}]}; +force_reg(nil=Lit, Reg) -> + {Reg,[{move,Lit,Reg}]}; force_reg({Kind,_}=R, _) when Kind =:= x; Kind =:= y -> {R,[]}. @@ -2017,9 +2031,7 @@ is_gc_bif(Bif, Args) -> %% new_label(St) -> {L,St}. new_label(#cg{lcount=Next}=St) -> - %% Advance the label counter by 2 to allow us to create - %% a label for 'or' by incrementing an existing label. - {Next,St#cg{lcount=Next+2}}. + {Next,St#cg{lcount=Next+1}}. %% call_line(tail|body, Func, Anno) -> [] | [{line,...}]. %% Produce a line instruction if it will be needed by the diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index ac2d943fef..2c898ba6f8 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -18,63 +18,167 @@ %% %CopyrightEnd% %% +%%% +%%% This is a collection of various optimizations that don't need a separate +%%% pass by themselves and/or are mutually beneficial to other passes. +%%% +%%% The optimizations are applied in "phases," each with a list of sub-passes +%%% to run. These sub-passes are applied on all functions in a module before +%%% moving on to the next phase, which lets us gather module-level information +%%% in one phase and then apply it in the next without having to risk working +%%% with incomplete information. +%%% +%%% Each sub-pass operates on a #st{} record and a func_info_db(), where the +%%% former is just a #b_function{} whose blocks can be represented either in +%%% linear or map form, and the latter is a map with information about all +%%% functions in the module (see beam_ssa_opt.hrl for more details). +%%% + -module(beam_ssa_opt). -export([module/2]). --include("beam_ssa.hrl"). --import(lists, [all/2,append/1,foldl/3,keyfind/3,member/2,reverse/1,reverse/2, - splitwith/2,takewhile/2,unzip/1]). +-include("beam_ssa_opt.hrl"). + +-import(lists, [all/2,append/1,duplicate/2,foldl/3,keyfind/3,member/2, + reverse/1,reverse/2, + splitwith/2,sort/1,takewhile/2,unzip/1]). + +-define(DEFAULT_REPETITIONS, 2). -spec module(beam_ssa:b_module(), [compile:option()]) -> {'ok',beam_ssa:b_module()}. -module(#b_module{body=Fs0}=Module, Opts) -> - Ps = passes(Opts), - Fs = functions(Fs0, Ps), - {ok,Module#b_module{body=Fs}}. +-record(st, {ssa :: [{beam_ssa:label(),beam_ssa:b_blk()}] | + beam_ssa:block_map(), + args :: [beam_ssa:b_var()], + cnt :: beam_ssa:label(), + anno :: beam_ssa:anno()}). +-type st_map() :: #{ func_id() => #st{} }. + +module(Module, Opts) -> + FuncDb0 = case proplists:get_value(no_module_opt, Opts, false) of + false -> build_func_db(Module); + true -> #{} + end, + + %% Passes that perform module-level optimizations are often aided by + %% optimizing callers before callees and vice versa, so we optimize all + %% functions in call order, flipping it as required. + StMap0 = build_st_map(Module), + Order = get_call_order_po(StMap0, FuncDb0), + + Phases = + [{Order, prologue_passes(Opts)}] ++ + repeat(Opts, repeated_passes(Opts), Order) ++ + [{Order, epilogue_passes(Opts)}], + + {StMap, _FuncDb} = foldl(fun({FuncIds, Ps}, {StMap, FuncDb}) -> + phase(FuncIds, Ps, StMap, FuncDb) + end, {StMap0, FuncDb0}, Phases), + + {ok, finish(Module, StMap)}. + +phase([FuncId | Ids], Ps, StMap, FuncDb0) -> + try + {St, FuncDb} = + compile:run_sub_passes(Ps, {map_get(FuncId, StMap), FuncDb0}), + + phase(Ids, Ps, StMap#{ FuncId => St }, FuncDb) + catch + Class:Error:Stack -> + #b_local{name=Name,arity=Arity} = FuncId, + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end; +phase([], _Ps, StMap, FuncDb) -> + {StMap, FuncDb}. + +%% Repeats the given passes, alternating the order between runs to make the +%% type pass more efficient. +repeat(Opts, Ps, OrderA) -> + Repeat = proplists:get_value(ssa_opt_repeat, Opts, ?DEFAULT_REPETITIONS), + OrderB = reverse(OrderA), + repeat_1(Repeat, Ps, OrderA, OrderB). -functions([F|Fs], Ps) -> - [function(F, Ps)|functions(Fs, Ps)]; -functions([], _Ps) -> []. +repeat_1(0, _Opts, _OrderA, _OrderB) -> + []; +repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 0 -> + [{OrderA, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)]; +repeat_1(N, Ps, OrderA, OrderB) when N > 0, N rem 2 =:= 1 -> + [{OrderB, Ps} | repeat_1(N - 1, Ps, OrderA, OrderB)]. --type b_blk() :: beam_ssa:b_blk(). --type b_var() :: beam_ssa:b_var(). --type label() :: beam_ssa:label(). +%% + +get_func_id(F) -> + {_Mod, Name, Arity} = beam_ssa:get_anno(func_info, F), + #b_local{name=#b_literal{val=Name}, arity=Arity}. + +-spec build_st_map(#b_module{}) -> st_map(). +build_st_map(#b_module{body=Fs}) -> + build_st_map_1(Fs, #{}). + +build_st_map_1([F | Fs], Map) -> + #b_function{anno=Anno,args=Args,cnt=Counter,bs=Bs} = F, + St = #st{anno=Anno,args=Args,cnt=Counter,ssa=Bs}, + build_st_map_1(Fs, Map#{ get_func_id(F) => St }); +build_st_map_1([], Map) -> + Map. + +-spec finish(#b_module{}, st_map()) -> #b_module{}. +finish(#b_module{body=Fs0}=Module, StMap) -> + Module#b_module{body=finish_1(Fs0, StMap)}. + +finish_1([F0 | Fs], StMap) -> + #st{anno=Anno,cnt=Counter,ssa=Blocks} = map_get(get_func_id(F0), StMap), + F = F0#b_function{anno=Anno,bs=Blocks,cnt=Counter}, + [F | finish_1(Fs, StMap)]; +finish_1([], _StMap) -> + []. + +%% --record(st, {ssa :: beam_ssa:block_map() | [{label(),b_blk()}], - args :: [b_var()], - cnt :: label()}). -define(PASS(N), {N,fun N/1}). -passes(Opts0) -> +prologue_passes(Opts) -> Ps = [?PASS(ssa_opt_split_blocks), ?PASS(ssa_opt_coalesce_phis), + ?PASS(ssa_opt_tail_phis), ?PASS(ssa_opt_element), ?PASS(ssa_opt_linearize), + ?PASS(ssa_opt_tuple_size), ?PASS(ssa_opt_record), - - %% Run ssa_opt_cse twice, because it will help ssa_opt_dead, - %% and ssa_opt_dead will help ssa_opt_cse. Run ssa_opt_live - %% twice, because it will help ssa_opt_dead and ssa_opt_dead - %% will help ssa_opt_live. - ?PASS(ssa_opt_cse), - ?PASS(ssa_opt_type), - ?PASS(ssa_opt_live), + ?PASS(ssa_opt_cse), %Helps the first type pass. + ?PASS(ssa_opt_type_start)], + passes_1(Ps, Opts). + +%% These passes all benefit from each other (in roughly this order), so they +%% are repeated as required. +repeated_passes(Opts) -> + Ps = [?PASS(ssa_opt_live), + ?PASS(ssa_opt_bs_puts), ?PASS(ssa_opt_dead), - ?PASS(ssa_opt_cse), %Second time. - ?PASS(ssa_opt_float), - ?PASS(ssa_opt_live), %Second time. + ?PASS(ssa_opt_cse), + ?PASS(ssa_opt_tail_phis), + ?PASS(ssa_opt_type_continue)], %Must run after ssa_opt_dead to + %clean up phi nodes. + passes_1(Ps, Opts). +epilogue_passes(Opts) -> + Ps = [?PASS(ssa_opt_type_finish), + ?PASS(ssa_opt_float), + ?PASS(ssa_opt_live), %One last time to clean up the + %mess left by the float pass. ?PASS(ssa_opt_bsm), ?PASS(ssa_opt_bsm_units), ?PASS(ssa_opt_bsm_shortcut), - ?PASS(ssa_opt_misc), - ?PASS(ssa_opt_tuple_size), ?PASS(ssa_opt_sw), ?PASS(ssa_opt_blockify), ?PASS(ssa_opt_sink), ?PASS(ssa_opt_merge_blocks), ?PASS(ssa_opt_trim_unreachable)], + passes_1(Ps, Opts). + +passes_1(Ps, Opts0) -> Negations = [{list_to_atom("no_"++atom_to_list(N)),N} || {N,_} <- Ps], Opts = proplists:substitute_negations(Negations, Opts0), @@ -86,36 +190,132 @@ passes(Opts0) -> {NoName,fun(S) -> S end} end || {Name,_}=P <- Ps]. -function(#b_function{anno=Anno,bs=Blocks0,args=Args,cnt=Count0}=F, Ps) -> +%% Builds a function information map with basic information about incoming and +%% outgoing local calls, as well as whether the function is exported. +-spec build_func_db(#b_module{}) -> func_info_db(). +build_func_db(#b_module{body=Fs,exports=Exports}) -> try - St = #st{ssa=Blocks0,args=Args,cnt=Count0}, - #st{ssa=Blocks,cnt=Count} = compile:run_sub_passes(Ps, St), - F#b_function{bs=Blocks,cnt=Count} + fdb_1(Fs, gb_sets:from_list(Exports), #{}) catch - Class:Error:Stack -> - #{func_info:={_,Name,Arity}} = Anno, - io:fwrite("Function: ~w/~w\n", [Name,Arity]), - erlang:raise(Class, Error, Stack) + %% All module-level optimizations are invalid when a NIF can override a + %% function, so we have to bail out. + throw:load_nif -> #{} end. +fdb_1([#b_function{ args=Args,bs=Bs }=F | Fs], Exports, FuncDb0) -> + Id = get_func_id(F), + + #b_local{name=#b_literal{val=Name}, arity=Arity} = Id, + Exported = gb_sets:is_element({Name, Arity}, Exports), + ArgTypes = duplicate(length(Args), #{}), + + FuncDb1 = case FuncDb0 of + %% We may have an entry already if someone's called us. + #{ Id := Info } -> + FuncDb0#{ Id := Info#func_info{ exported=Exported, + arg_types=ArgTypes }}; + #{} -> + FuncDb0#{ Id => #func_info{ exported=Exported, + arg_types=ArgTypes }} + end, + + FuncDb = beam_ssa:fold_rpo(fun(_L, #b_blk{is=Is}, FuncDb) -> + fdb_is(Is, Id, FuncDb) + end, FuncDb1, Bs), + + fdb_1(Fs, Exports, FuncDb); +fdb_1([], _Exports, FuncDb) -> + FuncDb. + +fdb_is([#b_set{op=call, + args=[#b_local{}=Callee | _]} | Is], + Caller, FuncDb) -> + fdb_is(Is, Caller, fdb_update(Caller, Callee, FuncDb)); +fdb_is([#b_set{op=call, + args=[#b_remote{mod=#b_literal{val=erlang}, + name=#b_literal{val=load_nif}}, + _Path, _LoadInfo]} | _Is], _Caller, _FuncDb) -> + throw(load_nif); +fdb_is([_ | Is], Caller, FuncDb) -> + fdb_is(Is, Caller, FuncDb); +fdb_is([], _Caller, FuncDb) -> + FuncDb. + +fdb_update(Caller, Callee, FuncDb) -> + CallerVertex = maps:get(Caller, FuncDb, #func_info{}), + CalleeVertex = maps:get(Callee, FuncDb, #func_info{}), + + Calls = ordsets:add_element(Callee, CallerVertex#func_info.out), + CalledBy = ordsets:add_element(Caller, CalleeVertex#func_info.in), + + FuncDb#{ Caller => CallerVertex#func_info{out=Calls}, + Callee => CalleeVertex#func_info{in=CalledBy} }. + +%% Returns the post-order of all local calls in this module. That is, it starts +%% with the functions that don't call any others and then walks up the call +%% chain. +%% +%% Functions where module-level optimization is disabled are added last in +%% arbitrary order. + +get_call_order_po(StMap, FuncDb) -> + Leaves = maps:fold(fun(Id, #func_info{out=[]}, Acc) -> + [Id | Acc]; + (_, _, Acc) -> + Acc + end, [], FuncDb), + + Order = gco_po_1(sort(Leaves), FuncDb, [], #{}), + + Order ++ maps:fold(fun(K, _V, Acc) -> + case is_map_key(K, FuncDb) of + false -> [K | Acc]; + true -> Acc + end + end, [], StMap). + +gco_po_1([Id | Ids], FuncDb, Children, Seen) when not is_map_key(Id, Seen) -> + [Id | gco_po_1(Ids, FuncDb, [Id | Children], Seen#{ Id => true })]; +gco_po_1([_Id | Ids], FuncDb, Children, Seen) -> + gco_po_1(Ids, FuncDb, Children, Seen); +gco_po_1([], FuncDb, [_|_]=Children, Seen) -> + gco_po_1(gco_po_parents(Children, FuncDb), FuncDb, [], Seen); +gco_po_1([], _FuncDb, [], _Seen) -> + []. + +gco_po_parents([Child | Children], FuncDb) -> + #{ Child := #func_info{in=Parents}} = FuncDb, + Parents ++ gco_po_parents(Children, FuncDb); +gco_po_parents([], _FuncDb) -> + []. + %%% %%% Trivial sub passes. %%% -ssa_opt_dead(#st{ssa=Linear}=St) -> - St#st{ssa=beam_ssa_dead:opt(Linear)}. +ssa_opt_dead({#st{ssa=Linear}=St, FuncDb}) -> + {St#st{ssa=beam_ssa_dead:opt(Linear)}, FuncDb}. + +ssa_opt_linearize({#st{ssa=Blocks}=St, FuncDb}) -> + {St#st{ssa=beam_ssa:linearize(Blocks)}, FuncDb}. + +ssa_opt_type_start({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) -> + {Linear, FuncDb} = beam_ssa_type:opt_start(Linear0, Args, Anno, FuncDb0), + {St0#st{ssa=Linear}, FuncDb}. -ssa_opt_linearize(#st{ssa=Blocks}=St) -> - St#st{ssa=beam_ssa:linearize(Blocks)}. +ssa_opt_type_continue({#st{ssa=Linear0,args=Args,anno=Anno}=St0, FuncDb0}) -> + {Linear, FuncDb} = beam_ssa_type:opt_continue(Linear0, Args, Anno, FuncDb0), + {St0#st{ssa=Linear}, FuncDb}. -ssa_opt_type(#st{ssa=Linear,args=Args}=St) -> - St#st{ssa=beam_ssa_type:opt(Linear, Args)}. +ssa_opt_type_finish({#st{args=Args,anno=Anno0}=St0, FuncDb0}) -> + {Anno, FuncDb} = beam_ssa_type:opt_finish(Args, Anno0, FuncDb0), + {St0#st{anno=Anno}, FuncDb}. -ssa_opt_blockify(#st{ssa=Linear}=St) -> - St#st{ssa=maps:from_list(Linear)}. +ssa_opt_blockify({#st{ssa=Linear}=St, FuncDb}) -> + {St#st{ssa=maps:from_list(Linear)}, FuncDb}. -ssa_opt_trim_unreachable(#st{ssa=Blocks}=St) -> - St#st{ssa=beam_ssa:trim_unreachable(Blocks)}. +ssa_opt_trim_unreachable({#st{ssa=Blocks}=St, FuncDb}) -> + {St#st{ssa=beam_ssa:trim_unreachable(Blocks)}, FuncDb}. %%% %%% Split blocks before certain instructions to enable more optimizations. @@ -127,14 +327,14 @@ ssa_opt_trim_unreachable(#st{ssa=Blocks}=St) -> %%% for sinking get_tuple_element instructions. %%% -ssa_opt_split_blocks(#st{ssa=Blocks0,cnt=Count0}=St) -> +ssa_opt_split_blocks({#st{ssa=Blocks0,cnt=Count0}=St, FuncDb}) -> P = fun(#b_set{op={bif,element}}) -> true; (#b_set{op=call}) -> true; (#b_set{op=make_fun}) -> true; (_) -> false end, {Blocks,Count} = beam_ssa:split_blocks(P, Blocks0, Count0), - St#st{ssa=Blocks,cnt=Count}. + {St#st{ssa=Blocks,cnt=Count}, FuncDb}. %%% %%% Coalesce phi nodes. @@ -158,10 +358,10 @@ ssa_opt_split_blocks(#st{ssa=Blocks0,cnt=Count0}=St) -> %%% different registers). %%% -ssa_opt_coalesce_phis(#st{ssa=Blocks0}=St) -> +ssa_opt_coalesce_phis({#st{ssa=Blocks0}=St, FuncDb}) -> Ls = beam_ssa:rpo(Blocks0), Blocks = c_phis_1(Ls, Blocks0), - St#st{ssa=Blocks}. + {St#st{ssa=Blocks}, FuncDb}. c_phis_1([L|Ls], Blocks0) -> case maps:get(L, Blocks0) of @@ -233,6 +433,160 @@ c_fix_branches([{_,Pred}|As], L, Blocks0) -> c_fix_branches([], _, Blocks) -> Blocks. %%% +%%% Eliminate phi nodes in the tail of a function. +%%% +%%% Try to eliminate short blocks that starts with a phi node +%%% and end in a return. For example: +%%% +%%% Result = phi { Res1, 4 }, { literal true, 5 } +%%% Ret = put_tuple literal ok, Result +%%% ret Ret +%%% +%%% The code in this block can be inserted at the end blocks 4 and +%%% 5. Thus, the following code can be inserted into block 4: +%%% +%%% Ret:1 = put_tuple literal ok, Res1 +%%% ret Ret:1 +%%% +%%% And the following code into block 5: +%%% +%%% Ret:2 = put_tuple literal ok, literal true +%%% ret Ret:2 +%%% +%%% Which can be further simplified to: +%%% +%%% ret literal {ok, true} +%%% +%%% This transformation may lead to more code improvements: +%%% +%%% - Stack trimming +%%% - Fewer test_heap instructions +%%% - Smaller stack frames +%%% + +ssa_opt_tail_phis({#st{ssa=SSA0,cnt=Count0}=St, FuncDb}) -> + {SSA,Count} = opt_tail_phis(SSA0, Count0), + {St#st{ssa=SSA,cnt=Count}, FuncDb}. + +opt_tail_phis(Blocks, Count) when is_map(Blocks) -> + opt_tail_phis(maps:values(Blocks), Blocks, Count); +opt_tail_phis(Linear0, Count0) when is_list(Linear0) -> + Blocks0 = maps:from_list(Linear0), + {Blocks,Count} = opt_tail_phis(Blocks0, Count0), + {beam_ssa:linearize(Blocks),Count}. + +opt_tail_phis([#b_blk{is=Is0,last=Last}|Bs], Blocks0, Count0) -> + case {Is0,Last} of + {[#b_set{op=phi,args=[_,_|_]}|_],#b_ret{arg=#b_var{}}=Ret} -> + {Phis,Is} = splitwith(fun(#b_set{op=Op}) -> Op =:= phi end, Is0), + case suitable_tail_ops(Is) of + true -> + {Blocks,Count} = opt_tail_phi(Phis, Is, Ret, + Blocks0, Count0), + opt_tail_phis(Bs, Blocks, Count); + false -> + opt_tail_phis(Bs, Blocks0, Count0) + end; + {_,_} -> + opt_tail_phis(Bs, Blocks0, Count0) + end; +opt_tail_phis([], Blocks, Count) -> + {Blocks,Count}. + +opt_tail_phi(Phis0, Is, Ret, Blocks0, Count0) -> + Phis = rel2fam(reduce_phis(Phis0)), + {Blocks,Count,Cost} = + foldl(fun(PhiArg, Acc) -> + opt_tail_phi_arg(PhiArg, Is, Ret, Acc) + end, {Blocks0,Count0,0}, Phis), + MaxCost = length(Phis) * 3 + 2, + if + Cost =< MaxCost -> + %% The transformation would cause at most a slight + %% increase in code size if no more optimizations + %% can be applied. + {Blocks,Count}; + true -> + %% The code size would be increased too much. + {Blocks0,Count0} + end. + +reduce_phis([#b_set{dst=PhiDst,args=PhiArgs}|Is]) -> + [{L,{PhiDst,Val}} || {Val,L} <- PhiArgs] ++ reduce_phis(Is); +reduce_phis([]) -> []. + +opt_tail_phi_arg({PredL,Sub0}, Is0, Ret0, {Blocks0,Count0,Cost0}) -> + Blk0 = map_get(PredL, Blocks0), + #b_blk{is=IsPrefix,last=#b_br{succ=Next,fail=Next}} = Blk0, + case is_exit_bif(IsPrefix) of + false -> + Sub1 = maps:from_list(Sub0), + {Is1,Count,Sub} = new_names(Is0, Sub1, Count0, []), + Is2 = [sub(I, Sub) || I <- Is1], + Cost = build_cost(Is2, Cost0), + Is = IsPrefix ++ Is2, + Ret = sub(Ret0, Sub), + Blk = Blk0#b_blk{is=Is,last=Ret}, + Blocks = Blocks0#{PredL:=Blk}, + {Blocks,Count,Cost}; + true -> + %% The block ends in a call to a function that + %% will cause an exception. + {Blocks0,Count0,Cost0+3} + end. + +is_exit_bif([#b_set{op=call, + args=[#b_remote{mod=#b_literal{val=Mod}, + name=#b_literal{val=Name}}|Args]}]) -> + erl_bifs:is_exit_bif(Mod, Name, length(Args)); +is_exit_bif(_) -> false. + +new_names([#b_set{dst=Dst}=I|Is], Sub0, Count0, Acc) -> + {NewDst,Count} = new_var(Dst, Count0), + Sub = Sub0#{Dst=>NewDst}, + new_names(Is, Sub, Count, [I#b_set{dst=NewDst}|Acc]); +new_names([], Sub, Count, Acc) -> + {reverse(Acc),Count,Sub}. + +suitable_tail_ops(Is) -> + all(fun(#b_set{op=Op}) -> + is_suitable_tail_op(Op) + end, Is). + +is_suitable_tail_op({bif,_}) -> true; +is_suitable_tail_op(put_list) -> true; +is_suitable_tail_op(put_tuple) -> true; +is_suitable_tail_op(_) -> false. + +build_cost([#b_set{op=put_list,args=Args}|Is], Cost) -> + case are_all_literals(Args) of + true -> + build_cost(Is, Cost); + false -> + build_cost(Is, Cost + 1) + end; +build_cost([#b_set{op=put_tuple,args=Args}|Is], Cost) -> + case are_all_literals(Args) of + true -> + build_cost(Is, Cost); + false -> + build_cost(Is, Cost + length(Args) + 1) + end; +build_cost([#b_set{op={bif,_},args=Args}|Is], Cost) -> + case are_all_literals(Args) of + true -> + build_cost(Is, Cost); + false -> + build_cost(Is, Cost + 1) + end; +build_cost([], Cost) -> Cost. + +are_all_literals(Args) -> + all(fun(#b_literal{}) -> true; + (_) -> false + end, Args). + +%%% %%% Order element/2 calls. %%% %%% Order an unbroken chain of element/2 calls for the same tuple @@ -241,7 +595,7 @@ c_fix_branches([], _, Blocks) -> Blocks. %%% be replaced with get_tuple_element/3 instructions. %%% -ssa_opt_element(#st{ssa=Blocks}=St) -> +ssa_opt_element({#st{ssa=Blocks}=St, FuncDb}) -> %% Collect the information about element instructions in this %% function. GetEls = collect_element_calls(beam_ssa:linearize(Blocks)), @@ -253,7 +607,7 @@ ssa_opt_element(#st{ssa=Blocks}=St) -> %% For each chain, swap the first element call with the %% element call with the highest index. - St#st{ssa=swap_element_calls(Chains, Blocks)}. + {St#st{ssa=swap_element_calls(Chains, Blocks)}, FuncDb}. collect_element_calls([{L,#b_blk{is=Is0,last=Last}}|Bs]) -> case {Is0,Last} of @@ -314,9 +668,9 @@ swap_element_calls_1([], _, Blocks) -> %%% when applicable. %%% -ssa_opt_record(#st{ssa=Linear}=St) -> +ssa_opt_record({#st{ssa=Linear}=St, FuncDb}) -> Blocks = maps:from_list(Linear), - St#st{ssa=record_opt(Linear, Blocks)}. + {St#st{ssa=record_opt(Linear, Blocks)}, FuncDb}. record_opt([{L,#b_blk{is=Is0,last=Last}=Blk0}|Bs], Blocks) -> Is = record_opt_is(Is0, Last, Blocks), @@ -400,9 +754,9 @@ is_tagged_tuple_4([], _, _) -> no. %%% subexpressions across instructions that clobber the X registers. %%% -ssa_opt_cse(#st{ssa=Linear}=St) -> +ssa_opt_cse({#st{ssa=Linear}=St, FuncDb}) -> M = #{0=>#{}}, - St#st{ssa=cse(Linear, #{}, M)}. + {St#st{ssa=cse(Linear, #{}, M)}, FuncDb}. cse([{L,#b_blk{is=Is0,last=Last0}=Blk}|Bs], Sub0, M0) -> Es0 = maps:get(L, M0), @@ -543,13 +897,13 @@ cse_suitable(#b_set{}) -> false. bs :: beam_ssa:block_map() }). -ssa_opt_float(#st{ssa=Linear0,cnt=Count0}=St) -> +ssa_opt_float({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> NonGuards0 = float_non_guards(Linear0), NonGuards = gb_sets:from_list(NonGuards0), Blocks = maps:from_list(Linear0), Fs = #fs{non_guards=NonGuards,bs=Blocks}, {Linear,Count} = float_opt(Linear0, Count0, Fs), - St#st{ssa=Linear,cnt=Count}. + {St#st{ssa=Linear,cnt=Count}, FuncDb}. float_non_guards([{L,#b_blk{is=Is}}|Bs]) -> case Is of @@ -787,15 +1141,20 @@ float_flush_regs(#fs{regs=Rs}) -> %%% with a cheaper instructions %%% -ssa_opt_live(#st{ssa=Linear}=St) -> - St#st{ssa=live_opt(reverse(Linear), #{}, [])}. +ssa_opt_live({#st{ssa=Linear0}=St, FuncDb}) -> + RevLinear = reverse(Linear0), + Blocks0 = maps:from_list(RevLinear), + Blocks = live_opt(RevLinear, #{}, Blocks0), + Linear = beam_ssa:linearize(Blocks), + {St#st{ssa=Linear}, FuncDb}. -live_opt([{L,Blk0}|Bs], LiveMap0, Acc) -> - Successors = beam_ssa:successors(Blk0), +live_opt([{L,Blk0}|Bs], LiveMap0, Blocks) -> + Blk1 = beam_ssa_share:block(Blk0, Blocks), + Successors = beam_ssa:successors(Blk1), Live0 = live_opt_succ(Successors, L, LiveMap0), - {Blk,Live} = live_opt_blk(Blk0, Live0), + {Blk,Live} = live_opt_blk(Blk1, Live0), LiveMap = live_opt_phis(Blk#b_blk.is, L, Live, LiveMap0), - live_opt(Bs, LiveMap, [{L,Blk}|Acc]); + live_opt(Bs, LiveMap, Blocks#{L:=Blk}); live_opt([], _, Acc) -> Acc. live_opt_succ([S|Ss], L, LiveMap) -> @@ -849,14 +1208,9 @@ live_opt_is([#b_set{op=succeeded,dst=SuccDst=SuccDstVar, #b_set{dst=Dst}=I|Is], Live0, Acc) -> case gb_sets:is_member(Dst, Live0) of true -> - case gb_sets:is_member(SuccDst, Live0) of - true -> - Live1 = gb_sets:add(Dst, Live0), - Live = gb_sets:delete_any(SuccDst, Live1), - live_opt_is([I|Is], Live, [SuccI|Acc]); - false -> - live_opt_is([I|Is], Live0, Acc) - end; + Live1 = gb_sets:add(Dst, Live0), + Live = gb_sets:delete_any(SuccDst, Live1), + live_opt_is([I|Is], Live, [SuccI|Acc]); false -> case live_opt_unused(I) of {replace,NewI0} -> @@ -896,24 +1250,32 @@ live_opt_unused(#b_set{op=get_map_element}=Set) -> live_opt_unused(_) -> keep. %%% -%%% Optimize binary matching instructions. +%%% Optimize binary matching. +%%% +%%% * If the value of segment is never extracted, rewrite +%%% to a bs_skip instruction. +%%% +%%% * Coalesce adjacent bs_skip instructions and skip instructions +%%% with bs_test_tail. %%% -ssa_opt_bsm(#st{ssa=Linear}=St) -> +ssa_opt_bsm({#st{ssa=Linear}=St, FuncDb}) -> Extracted0 = bsm_extracted(Linear), Extracted = cerl_sets:from_list(Extracted0), - St#st{ssa=bsm_skip(Linear, Extracted)}. + {St#st{ssa=bsm_skip(Linear, Extracted)}, FuncDb}. -bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs], Extracted) -> +bsm_skip([{L,#b_blk{is=Is0}=Blk}|Bs0], Extracted) -> + Bs = bsm_skip(Bs0, Extracted), Is = bsm_skip_is(Is0, Extracted), - [{L,Blk#b_blk{is=Is}}|bsm_skip(Bs, Extracted)]; + coalesce_skips({L,Blk#b_blk{is=Is}}, Bs); bsm_skip([], _) -> []. bsm_skip_is([I0|Is], Extracted) -> case I0 of - #b_set{op=bs_match,args=[#b_literal{val=string}|_]} -> - [I0|bsm_skip_is(Is, Extracted)]; - #b_set{op=bs_match,dst=Ctx,args=[Type,PrevCtx|Args0]} -> + #b_set{op=bs_match, + dst=Ctx, + args=[#b_literal{val=T}=Type,PrevCtx|Args0]} + when T =/= string, T =/= skip -> I = case cerl_sets:is_element(Ctx, Extracted) of true -> I0; @@ -937,18 +1299,75 @@ bsm_extracted([{_,#b_blk{is=Is}}|Bs]) -> end; bsm_extracted([]) -> []. +coalesce_skips({L,#b_blk{is=[#b_set{op=bs_extract}=Extract|Is0], + last=Last0}=Blk0}, Bs0) -> + case coalesce_skips_is(Is0, Last0, Bs0) of + not_possible -> + [{L,Blk0}|Bs0]; + {Is,Last,Bs} -> + Blk = Blk0#b_blk{is=[Extract|Is],last=Last}, + [{L,Blk}|Bs] + end; +coalesce_skips({L,#b_blk{is=Is0,last=Last0}=Blk0}, Bs0) -> + case coalesce_skips_is(Is0, Last0, Bs0) of + not_possible -> + [{L,Blk0}|Bs0]; + {Is,Last,Bs} -> + Blk = Blk0#b_blk{is=Is,last=Last}, + [{L,Blk}|Bs] + end. + +coalesce_skips_is([#b_set{op=bs_match, + args=[#b_literal{val=skip}, + Ctx0,Type,Flags, + #b_literal{val=Size0}, + #b_literal{val=Unit0}]}=Skip0, + #b_set{op=succeeded}], + #b_br{succ=L2,fail=Fail}=Br0, + Bs0) when is_integer(Size0) -> + case Bs0 of + [{L2,#b_blk{is=[#b_set{op=bs_match, + dst=SkipDst, + args=[#b_literal{val=skip},_,_,_, + #b_literal{val=Size1}, + #b_literal{val=Unit1}]}, + #b_set{op=succeeded}=Succeeded], + last=#b_br{fail=Fail}=Br}}|Bs] when is_integer(Size1) -> + SkipBits = Size0 * Unit0 + Size1 * Unit1, + Skip = Skip0#b_set{dst=SkipDst, + args=[#b_literal{val=skip},Ctx0, + Type,Flags, + #b_literal{val=SkipBits}, + #b_literal{val=1}]}, + Is = [Skip,Succeeded], + {Is,Br,Bs}; + [{L2,#b_blk{is=[#b_set{op=bs_test_tail, + args=[_Ctx,#b_literal{val=TailSkip}]}], + last=#b_br{succ=NextSucc,fail=Fail}}}|Bs] -> + SkipBits = Size0 * Unit0, + TestTail = Skip0#b_set{op=bs_test_tail, + args=[Ctx0,#b_literal{val=SkipBits+TailSkip}]}, + Br = Br0#b_br{bool=TestTail#b_set.dst,succ=NextSucc}, + Is = [TestTail], + {Is,Br,Bs}; + _ -> + not_possible + end; +coalesce_skips_is(_, _, _) -> + not_possible. + %%% %%% Short-cutting binary matching instructions. %%% -ssa_opt_bsm_shortcut(#st{ssa=Linear}=St) -> +ssa_opt_bsm_shortcut({#st{ssa=Linear}=St, FuncDb}) -> Positions = bsm_positions(Linear, #{}), case map_size(Positions) of 0 -> %% No binary matching instructions. - St; + {St, FuncDb}; _ -> - St#st{ssa=bsm_shortcut(Linear, Positions)} + {St#st{ssa=bsm_shortcut(Linear, Positions)}, FuncDb} end. bsm_positions([{L,#b_blk{is=Is,last=Last}}|Bs], PosMap0) -> @@ -1010,8 +1429,8 @@ bsm_shortcut([], _PosMap) -> []. %%% Eliminate redundant bs_test_unit2 instructions. %%% -ssa_opt_bsm_units(#st{ssa=Linear}=St) -> - St#st{ssa=bsm_units(Linear, #{})}. +ssa_opt_bsm_units({#st{ssa=Linear}=St, FuncDb}) -> + {St#st{ssa=bsm_units(Linear, #{})}, FuncDb}. bsm_units([{L,#b_blk{last=#b_br{succ=Succ,fail=Fail}}=Block0} | Bs], UnitMaps0) -> UnitsIn = maps:get(L, UnitMaps0, #{}), @@ -1111,91 +1530,172 @@ bsm_units_join_1([], _MapA, Right) -> Right. %%% -%%% Miscellanous optimizations in execution order. +%%% Optimize binary construction. +%%% +%%% If an integer segment or a float segment has a literal size and +%%% a literal value, convert to a binary segment. Coalesce adjacent +%%% literal binary segments. Literal binary segments will be converted +%%% to bs_put_string instructions in later pass. %%% -ssa_opt_misc(#st{ssa=Linear}=St) -> - St#st{ssa=misc_opt(Linear, #{})}. +ssa_opt_bs_puts({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> + {Linear,Count} = opt_bs_puts(Linear0, Count0, []), + {St#st{ssa=Linear,cnt=Count}, FuncDb}. -misc_opt([{L,#b_blk{is=Is0,last=Last0}=Blk0}|Bs], Sub0) -> - {Is,Sub} = misc_opt_is(Is0, Sub0, []), - Last = sub(Last0, Sub), - Blk = Blk0#b_blk{is=Is,last=Last}, - [{L,Blk}|misc_opt(Bs, Sub)]; -misc_opt([], _) -> []. +opt_bs_puts([{L,#b_blk{is=Is}=Blk0}|Bs], Count0, Acc0) -> + case Is of + [#b_set{op=bs_put}=I0] -> + case opt_bs_put(L, I0, Blk0, Count0, Acc0) of + not_possible -> + opt_bs_puts(Bs, Count0, [{L,Blk0}|Acc0]); + {Count,Acc1} -> + Acc = opt_bs_puts_merge(Acc1), + opt_bs_puts(Bs, Count, Acc) + end; + _ -> + opt_bs_puts(Bs, Count0, [{L,Blk0}|Acc0]) + end; +opt_bs_puts([], Count, Acc) -> + {reverse(Acc),Count}. -misc_opt_is([#b_set{op=phi}=I0|Is], Sub0, Acc) -> - #b_set{dst=Dst,args=Args} = I = sub(I0, Sub0), - case all_same(Args) of +opt_bs_puts_merge([{L1,#b_blk{is=Is}=Blk0},{L2,#b_blk{is=AccIs}}=BAcc|Acc]) -> + case {AccIs,Is} of + {[#b_set{op=bs_put, + args=[#b_literal{val=binary}, + #b_literal{}, + #b_literal{val=Bin0}, + #b_literal{val=all}, + #b_literal{val=1}]}], + [#b_set{op=bs_put, + args=[#b_literal{val=binary}, + #b_literal{}, + #b_literal{val=Bin1}, + #b_literal{val=all}, + #b_literal{val=1}]}=I0]} -> + %% Coalesce the two segments to one. + Bin = <<Bin0/bitstring,Bin1/bitstring>>, + I = I0#b_set{args=bs_put_args(binary, Bin, all)}, + Blk = Blk0#b_blk{is=[I]}, + [{L2,Blk}|Acc]; + {_,_} -> + [{L1,Blk0},BAcc|Acc] + end. + +opt_bs_put(L, I0, #b_blk{last=Br0}=Blk0, Count0, Acc) -> + case opt_bs_put(I0) of + [Bin] when is_bitstring(Bin) -> + Args = bs_put_args(binary, Bin, all), + I = I0#b_set{args=Args}, + Blk = Blk0#b_blk{is=[I]}, + {Count0,[{L,Blk}|Acc]}; + [{int,Int,Size},Bin] when is_bitstring(Bin) -> + %% Construct a bs_put_integer instruction following + %% by a bs_put_binary instruction. + IntArgs = bs_put_args(integer, Int, Size), + BinArgs = bs_put_args(binary, Bin, all), + {BinL,BinVarNum} = {Count0,Count0+1}, + Count = Count0 + 2, + BinVar = #b_var{name={'@ssa_bool',BinVarNum}}, + BinI = I0#b_set{dst=BinVar,args=BinArgs}, + BinBlk = Blk0#b_blk{is=[BinI],last=Br0#b_br{bool=BinVar}}, + IntI = I0#b_set{args=IntArgs}, + IntBlk = Blk0#b_blk{is=[IntI],last=Br0#b_br{succ=BinL}}, + {Count,[{BinL,BinBlk},{L,IntBlk}|Acc]}; + not_possible -> + not_possible + end. + +opt_bs_put(#b_set{args=[#b_literal{val=binary},_,#b_literal{val=Val}, + #b_literal{val=all},#b_literal{val=Unit}]}) + when is_bitstring(Val) -> + if + bit_size(Val) rem Unit =:= 0 -> + [Val]; true -> - %% Eliminate the phi node if there is just one source - %% value or if the values are identical. - [{Val,_}|_] = Args, - Sub = Sub0#{Dst=>Val}, - misc_opt_is(Is, Sub, Acc); - false -> - misc_opt_is(Is, Sub0, [I|Acc]) - end; -misc_opt_is([#b_set{op={bif,'and'}}=I0], Sub, Acc) -> - #b_set{dst=Dst,args=Args} = I = sub(I0, Sub), - case eval_and(Args) of - error -> - misc_opt_is([], Sub, [I|Acc]); - Val -> - misc_opt_is([], Sub#{Dst=>Val}, Acc) - end; -misc_opt_is([#b_set{op={bif,'or'}}=I0], Sub, Acc) -> - #b_set{dst=Dst,args=Args} = I = sub(I0, Sub), - case eval_or(Args) of - error -> - misc_opt_is([], Sub, [I|Acc]); - Val -> - misc_opt_is([], Sub#{Dst=>Val}, Acc) + not_possible end; -misc_opt_is([#b_set{}=I0|Is], Sub, Acc) -> - #b_set{op=Op,dst=Dst,args=Args} = I = sub(I0, Sub), - case make_literal(Op, Args) of - #b_literal{}=Literal -> - misc_opt_is(Is, Sub#{Dst=>Literal}, Acc); - error -> - misc_opt_is(Is, Sub, [I|Acc]) +opt_bs_put(#b_set{args=[#b_literal{val=Type},#b_literal{val=Flags}, + #b_literal{val=Val},#b_literal{val=Size}, + #b_literal{val=Unit}]}=I0) when is_integer(Size) -> + EffectiveSize = Size * Unit, + if + EffectiveSize > 0 -> + case {Type,opt_bs_put_endian(Flags)} of + {integer,big} when is_integer(Val) -> + if + EffectiveSize < 64 -> + [<<Val:EffectiveSize>>]; + true -> + opt_bs_put_split_int(Val, EffectiveSize) + end; + {integer,little} when is_integer(Val), EffectiveSize < 128 -> + %% To avoid an explosion in code size, we only try + %% to optimize relatively small fields. + <<Int:EffectiveSize>> = <<Val:EffectiveSize/little>>, + Args = bs_put_args(Type, Int, EffectiveSize), + I = I0#b_set{args=Args}, + opt_bs_put(I); + {binary,_} when is_bitstring(Val) -> + <<Bitstring:EffectiveSize/bits,_/bits>> = Val, + [Bitstring]; + {float,Endian} -> + try + [opt_bs_put_float(Val, EffectiveSize, Endian)] + catch error:_ -> + not_possible + end; + {_,_} -> + not_possible + end; + true -> + not_possible end; -misc_opt_is([], Sub, Acc) -> - {reverse(Acc),Sub}. +opt_bs_put(#b_set{}) -> not_possible. -all_same([{H,_}|T]) -> - all(fun({E,_}) -> E =:= H end, T). - -make_literal(put_tuple, Args) -> - case make_literal_list(Args, []) of - error -> - error; - List -> - #b_literal{val=list_to_tuple(List)} - end; -make_literal(put_list, [#b_literal{val=H},#b_literal{val=T}]) -> - #b_literal{val=[H|T]}; -make_literal(_, _) -> error. +opt_bs_put_float(N, Sz, Endian) -> + case Endian of + big -> <<N:Sz/big-float-unit:1>>; + little -> <<N:Sz/little-float-unit:1>> + end. -make_literal_list([#b_literal{val=H}|T], Acc) -> - make_literal_list(T, [H|Acc]); -make_literal_list([_|_], _) -> - error; -make_literal_list([], Acc) -> - reverse(Acc). - -eval_and(Args) -> - case Args of - [_,#b_literal{val=false}=Res] -> Res; - [Res,#b_literal{val=true}] -> Res; - [_,_] -> error +bs_put_args(Type, Val, Size) -> + [#b_literal{val=Type}, + #b_literal{val=[unsigned,big]}, + #b_literal{val=Val}, + #b_literal{val=Size}, + #b_literal{val=1}]. + +opt_bs_put_endian([big=E|_]) -> E; +opt_bs_put_endian([little=E|_]) -> E; +opt_bs_put_endian([native=E|_]) -> E; +opt_bs_put_endian([_|Fs]) -> opt_bs_put_endian(Fs). + +opt_bs_put_split_int(Int, Size) -> + Pos = opt_bs_put_split_int_1(Int, 0, Size - 1), + UpperSize = Size - Pos, + if + Pos =:= 0 -> + %% Value is 0 or -1 -- keep the original instruction. + not_possible; + UpperSize < 64 -> + %% No or few leading zeroes or ones. + [<<Int:Size>>]; + true -> + %% There are 64 or more leading ones or zeroes in + %% the resulting binary. Split into two separate + %% segments to avoid an explosion in code size. + [{int,Int bsr Pos,UpperSize},<<Int:Pos>>] end. -eval_or(Args) -> - case Args of - [Res,#b_literal{val=false}] -> Res; - [_,#b_literal{val=true}=Res] -> Res; - [_,_] -> error +opt_bs_put_split_int_1(_Int, L, R) when L > R -> + 8 * ((L + 7) div 8); +opt_bs_put_split_int_1(Int, L, R) -> + Mid = (L + R) div 2, + case Int bsr Mid of + Upper when Upper =:= 0; Upper =:= -1 -> + opt_bs_put_split_int_1(Int, L, Mid - 1); + _ -> + opt_bs_put_split_int_1(Int, Mid + 1, R) end. %%% @@ -1258,9 +1758,9 @@ eval_or(Args) -> %%% is_tuple_of_arity instruction by the loader. %%% -ssa_opt_tuple_size(#st{ssa=Linear0,cnt=Count0}=St) -> +ssa_opt_tuple_size({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> {Linear,Count} = opt_tup_size(Linear0, Count0, []), - St#st{ssa=Linear,cnt=Count}. + {St#st{ssa=Linear,cnt=Count}, FuncDb}. opt_tup_size([{L,#b_blk{is=Is,last=Last}=Blk}|Bs], Count0, Acc0) -> case {Is,Last} of @@ -1333,9 +1833,9 @@ opt_tup_size_is([], _, _, _Acc) -> none. %%% is 'true' or 'false' can be rewritten to a is_boolean test. %%% -ssa_opt_sw(#st{ssa=Linear0,cnt=Count0}=St) -> +ssa_opt_sw({#st{ssa=Linear0,cnt=Count0}=St, FuncDb}) -> {Linear,Count} = opt_sw(Linear0, #{}, Count0, []), - St#st{ssa=Linear,cnt=Count}. + {St#st{ssa=Linear,cnt=Count}, FuncDb}. opt_sw([{L,#b_blk{is=Is,last=#b_switch{}=Last0}=Blk0}|Bs], Phis0, Count0, Acc) -> Phis = opt_sw_phis(Is, Phis0), @@ -1426,9 +1926,10 @@ opt_sw_literals([], Acc) -> Acc. %%% Merge blocks. %%% -ssa_opt_merge_blocks(#st{ssa=Blocks}=St) -> +ssa_opt_merge_blocks({#st{ssa=Blocks}=St, FuncDb}) -> Preds = beam_ssa:predecessors(Blocks), - St#st{ssa=merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks)}. + Merged = merge_blocks_1(beam_ssa:rpo(Blocks), Preds, Blocks), + {St#st{ssa=Merged}, FuncDb}. merge_blocks_1([L|Ls], Preds0, Blocks0) -> case Preds0 of @@ -1438,6 +1939,7 @@ merge_blocks_1([L|Ls], Preds0, Blocks0) -> true -> #b_blk{is=Is0} = Blk0, #b_blk{is=Is1} = Blk1, + verify_merge_is(Is1), Is = Is0 ++ Is1, Blk = Blk1#b_blk{is=Is}, Blocks1 = maps:remove(L, Blocks0), @@ -1463,6 +1965,13 @@ merge_update_preds([], _, _, Preds) -> Preds. rename_label(From, From, To) -> To; rename_label(Lbl, _, _) -> Lbl. +verify_merge_is([#b_set{op=Op}|_]) -> + %% The merged block has only one predecessor, so it should not have any phi + %% nodes. + true = Op =/= phi; %Assertion. +verify_merge_is(_) -> + ok. + is_merge_allowed(_, _, #b_blk{is=[#b_set{op=peek_message}|_]}) -> false; is_merge_allowed(L, Blk0, #b_blk{}) -> @@ -1487,7 +1996,7 @@ is_merge_allowed(L, Blk0, #b_blk{}) -> %%% extracted values. %%% -ssa_opt_sink(#st{ssa=Blocks0}=St) -> +ssa_opt_sink({#st{ssa=Blocks0}=St, FuncDb}) -> Linear = beam_ssa:linearize(Blocks0), %% Create a map with all variables that define get_tuple_element @@ -1528,7 +2037,7 @@ ssa_opt_sink(#st{ssa=Blocks0}=St) -> From = maps:get(V, Defs), move_defs(V, From, To, A) end, Blocks0, DefLoc), - St#st{ssa=Blocks}. + {St#st{ssa=Blocks}, FuncDb}. def_blocks([{L,#b_blk{is=Is}}|Bs]) -> def_blocks_is(Is, L, def_blocks(Bs)); @@ -1763,3 +2272,9 @@ sub_arg(Old, Sub) -> #{Old:=New} -> New; #{} -> Old end. + +new_var(#b_var{name={Base,N}}, Count) -> + true = is_integer(N), %Assertion. + {#b_var{name={Base,Count}},Count+1}; +new_var(#b_var{name=Base}, Count) -> + {#b_var{name={Base,Count}},Count+1}. diff --git a/lib/compiler/src/beam_ssa_opt.hrl b/lib/compiler/src/beam_ssa_opt.hrl new file mode 100644 index 0000000000..37711a6f48 --- /dev/null +++ b/lib/compiler/src/beam_ssa_opt.hrl @@ -0,0 +1,53 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019. 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% +%% + +-include("beam_ssa.hrl"). + +-record(func_info, + {%% Local calls going in/out of this function. + in = ordsets:new() :: ordsets:ordset(func_id()), + out = ordsets:new() :: ordsets:ordset(func_id()), + + %% Whether the function is exported or not; some optimizations may + %% need to be suppressed if it is. + exported = true :: boolean(), + + %% The inferred types of each argument (as opposed to parameter), + %% indexed by call site. + %% + %% This is more effective than the naive approach of joining into a + %% "parameter_type" as we go as it lets us narrow parameter types + %% without having to visit all callers on each pass, which helps a lot + %% when dealing with co-recursive functions. + arg_types = [] :: list(arg_type_map()), + + %% The inferred return type of this function, this is either [type()] + %% or [] to note absence. + ret_type = [] :: list()}). + +-type arg_key() :: {CallerId :: func_id(), + CallDst :: beam_ssa:b_var()}. +-type arg_type_map() :: #{ arg_key() => term() }. + +%% Per-function metadata used by various optimization passes to perform +%% module-level optimization. If a function is absent it means that +%% module-level optimization has been turned off for said function. +-type func_id() :: beam_ssa:b_local(). +-type func_info_db() :: #{ func_id() => #func_info{} }. diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl index 32232e9b9f..fde1118c29 100644 --- a/lib/compiler/src/beam_ssa_pre_codegen.erl +++ b/lib/compiler/src/beam_ssa_pre_codegen.erl @@ -72,7 +72,7 @@ -import(lists, [all/2,any/2,append/1,duplicate/2, foldl/3,last/1,map/2,member/2,partition/2, - reverse/1,reverse/2,sort/1,zip/2]). + reverse/1,reverse/2,sort/1,splitwith/2,zip/2]). -spec module(beam_ssa:b_module(), [compile:option()]) -> {'ok',beam_ssa:b_module()}. @@ -1031,7 +1031,7 @@ need_frame_1([#b_set{op=call,args=[Func|_]}|Is], Context) -> case Func of #b_remote{mod=#b_literal{val=Mod}, name=#b_literal{val=Name}, - arity=Arity} -> + arity=Arity} when is_atom(Mod), is_atom(Name) -> case erl_bifs:is_exit_bif(Mod, Name, Arity) of true -> false; @@ -1993,19 +1993,35 @@ reserve_zregs(Blocks, Intervals, Res) -> end, beam_ssa:fold_rpo(F, [0], Res, Blocks). +reserve_zreg([#b_set{op=call,dst=Dst}], + #b_br{bool=Dst}, _ShortLived, A) -> + %% If type optimization has determined that the result of a call can be + %% used directly in a branch, we must avoid reserving a z register or code + %% generation will fail. + A; reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}, - #b_set{op={bif,'=:='},args=[Dst,Val]}], _Last, ShortLived, A0) -> - case Val of - #b_literal{val=Arity} when Arity bsr 32 =:= 0 -> + #b_set{op={bif,'=:='},args=[Dst,Val]}], Last, ShortLived, A0) -> + case {Val,Last} of + {#b_literal{val=Arity},#b_br{bool=#b_var{}}} when Arity bsr 32 =:= 0 -> %% These two instructions can be combined to a test_arity %% instruction provided that the arity variable is short-lived. reserve_zreg_1(Dst, ShortLived, A0); - _ -> + {_,_} -> + %% Either the arity is too big, or the boolean value is not + %% used in a conditional branch. A0 end; reserve_zreg([#b_set{op={bif,tuple_size},dst=Dst}], #b_switch{}, ShortLived, A) -> reserve_zreg_1(Dst, ShortLived, A); +reserve_zreg([#b_set{op={bif,'xor'}}], _Last, _ShortLived, A) -> + %% There is no short, easy way to rewrite 'xor' to a series of + %% test instructions. + A; +reserve_zreg([#b_set{op={bif,is_record}}], _Last, _ShortLived, A) -> + %% There is no short, easy way to rewrite is_record/2 to a series of + %% test instructions. + A; reserve_zreg([#b_set{op=Op,dst=Dst}|Is], Last, ShortLived, A0) -> IsZReg = case Op of bs_match_string -> true; @@ -2070,23 +2086,95 @@ reserve_freg([], Res) -> Res. %% will allocate the lowest free X register for the variable. reserve_xregs(Blocks, Res) -> - F = fun(L, #b_blk{is=Is,last=Last}, R) -> - {Xs0,Used0} = reserve_terminator(L, Last, Blocks, R), - reserve_xregs_is(reverse(Is), R, Xs0, Used0) - end, - beam_ssa:fold_po(F, Res, Blocks). - + Ls = reverse(beam_ssa:rpo(Blocks)), + reserve_xregs(Ls, Blocks, #{}, Res). + +reserve_xregs([L|Ls], Blocks, XsMap0, Res0) -> + #b_blk{anno=Anno,is=Is0,last=Last} = map_get(L, Blocks), + + %% Calculate mapping from variable name to the preferred + %% register. + Xs0 = reserve_terminator(L, Is0, Last, Blocks, XsMap0, Res0), + + %% We need to figure out where the code generator will + %% place instructions that will do a garbage collection. + %% Insert 'gc' markers as pseudo-instructions in the + %% instruction sequence. + Is1 = reverse(Is0), + Is2 = res_place_gc_instrs(Is1, []), + Is = res_place_allocate(Anno, Is2), + + %% Add register hints for variables that are defined + %% in the (reversed) instruction sequence. + {Res,Xs} = reserve_xregs_is(Is, Res0, Xs0, []), + + XsMap = XsMap0#{L=>Xs}, + reserve_xregs(Ls, Blocks, XsMap, Res); +reserve_xregs([], _, _, Res) -> Res. + +%% Insert explicit 'gc' markers points where there will +%% be a garbage collection. (Note that the instruction +%% sequence passed to this function is reversed.) + +res_place_gc_instrs([#b_set{op=phi}=I|Is], Acc) -> + res_place_gc_instrs(Is, [I|Acc]); +res_place_gc_instrs([#b_set{op=Op}=I|Is], Acc) + when Op =:= call; Op =:= make_fun -> + case Acc of + [] -> + res_place_gc_instrs(Is, [I|Acc]); + [GC|_] when GC =:= gc; GC =:= test_heap -> + res_place_gc_instrs(Is, [I,gc|Acc]); + [_|_] -> + res_place_gc_instrs(Is, [I,gc|Acc]) + end; +res_place_gc_instrs([#b_set{op=Op,args=Args}=I|Is], Acc0) -> + case beam_ssa_codegen:classify_heap_need(Op, Args) of + neutral -> + case Acc0 of + [test_heap|Acc] -> + res_place_gc_instrs(Is, [test_heap,I|Acc]); + Acc -> + res_place_gc_instrs(Is, [I|Acc]) + end; + {put,_} -> + case Acc0 of + [test_heap|Acc] -> + res_place_gc_instrs(Is, [test_heap,I|Acc]); + Acc -> + res_place_gc_instrs(Is, [test_heap,I|Acc]) + end; + _ -> + res_place_gc_instrs(Is, [gc,I|Acc0]) + end; +res_place_gc_instrs([], Acc) -> + %% Reverse and replace 'test_heap' markers with 'gc'. + %% (The distinction is no longer useful.) + res_place_gc_instrs_rev(Acc, []). + +res_place_gc_instrs_rev([test_heap|Is], [gc|_]=Acc) -> + res_place_gc_instrs_rev(Is, Acc); +res_place_gc_instrs_rev([test_heap|Is], Acc) -> + res_place_gc_instrs_rev(Is, [gc|Acc]); +res_place_gc_instrs_rev([gc|Is], [gc|_]=Acc) -> + res_place_gc_instrs_rev(Is, Acc); +res_place_gc_instrs_rev([I|Is], Acc) -> + res_place_gc_instrs_rev(Is, [I|Acc]); +res_place_gc_instrs_rev([], Acc) -> Acc. + +res_place_allocate(#{yregs:=_}, Is) -> + %% There will be an 'allocate' instruction inserted here. + Is ++ [gc]; +res_place_allocate(#{}, Is) -> Is. + +reserve_xregs_is([gc|Is], Res, Xs0, Used) -> + %% At this point, the code generator will place an instruction + %% that does a garbage collection. We must prune the remembered + %% registers. + Xs = res_xregs_prune(Xs0, Used, Res), + reserve_xregs_is(Is, Res, Xs, Used); reserve_xregs_is([#b_set{op=Op,dst=Dst,args=Args}=I|Is], Res0, Xs0, Used0) -> - Xs1 = case is_gc_safe(I) of - true -> - Xs0; - false -> - %% There may be a garbage collection after executing this - %% instruction. We will need prune the list of preferred - %% X registers. - res_xregs_prune(Xs0, Used0, Res0) - end, - Res = reserve_xreg(Dst, Xs1, Res0), + Res = reserve_xreg(Dst, Xs0, Res0), Used1 = ordsets:union(Used0, beam_ssa:used(I)), Used = ordsets:del_element(Dst, Used1), case Op of @@ -2097,28 +2185,74 @@ reserve_xregs_is([#b_set{op=Op,dst=Dst,args=Args}=I|Is], Res0, Xs0, Used0) -> Xs = reserve_call_args(tl(Args)), reserve_xregs_is(Is, Res, Xs, Used); _ -> - reserve_xregs_is(Is, Res, Xs1, Used) + reserve_xregs_is(Is, Res, Xs0, Used) end; -reserve_xregs_is([], Res, _Xs, _Used) -> Res. - -reserve_terminator(L, #b_br{bool=#b_literal{val=true},succ=Succ}, Blocks, Res) -> - case maps:get(Succ, Blocks) of +reserve_xregs_is([], Res, Xs, _Used) -> + {Res,Xs}. + +%% Pick up register hints from the successors of this blocks. +reserve_terminator(_L, _Is, #b_br{bool=#b_var{},succ=Succ,fail=?BADARG_BLOCK}, + _Blocks, XsMap, _Res) -> + %% We know that no variables are used at ?BADARG_BLOCK, so + %% any register hints from the success blocks are safe to use. + map_get(Succ, XsMap); +reserve_terminator(L, Is, #b_br{bool=#b_var{},succ=Succ,fail=Fail}, + Blocks, XsMap, Res) when Succ =/= Fail -> + #{Succ:=SuccBlk,Fail:=FailBlk} = Blocks, + case {SuccBlk,FailBlk} of + {#b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}}, + #b_blk{is=[],last=#b_br{succ=PhiL,fail=PhiL}}} -> + %% Both branches ultimately transfer to the same + %% block (via two blocks with no instructions). + %% Pick up register hints from the phi nodes + %% in the common block. + #{PhiL:=#b_blk{is=PhiIs}} = Blocks, + Xs = res_xregs_from_phi(PhiIs, Succ, Res, #{}), + res_xregs_from_phi(PhiIs, Fail, Res, Xs); + {_,_} when Is =/= [] -> + case last(Is) of + #b_set{op=succeeded,args=[Arg]} -> + %% We know that Arg will not be used at the failure + %% label, so we can pick up register hints from the + %% success label. + Br = #b_br{bool=#b_literal{val=true},succ=Succ,fail=Succ}, + case reserve_terminator(L, [], Br, Blocks, XsMap, Res) of + #{Arg:=Reg} -> #{Arg=>Reg}; + #{} -> #{} + end; + _ -> + %% Register hints from the success block may not + %% be safe at the failure block, and vice versa. + #{} + end; + {_,_} -> + %% Register hints from the success block may not + %% be safe at the failure block, and vice versa. + #{} + end; +reserve_terminator(L, Is, #b_br{bool=#b_literal{val=true},succ=Succ}, + Blocks, XsMap, Res) -> + case map_get(Succ, Blocks) of #b_blk{is=[],last=Last} -> - reserve_terminator(Succ, Last, Blocks, Res); - #b_blk{is=[_|_]=Is} -> - {res_xregs_from_phi(Is, L, Res, #{}),[]} + reserve_terminator(Succ, Is, Last, Blocks, XsMap, Res); + #b_blk{is=[_|_]=PhiIs} -> + res_xregs_from_phi(PhiIs, L, Res, #{}) end; -reserve_terminator(_, Last, _, _) -> - {#{},beam_ssa:used(Last)}. +reserve_terminator(_, _, _, _, _, _) -> #{}. +%% Pick up a reservation from a phi node. res_xregs_from_phi([#b_set{op=phi,dst=Dst,args=Args}|Is], Pred, Res, Acc) -> case [V || {#b_var{}=V,L} <- Args, L =:= Pred] of [] -> + %% The value of the phi node for this predecessor + %% is a literal. Nothing to do here. res_xregs_from_phi(Is, Pred, Res, Acc); [V] -> case Res of #{Dst:={prefer,Reg}} -> + %% Try placing V in the same register as for + %% the phi node. res_xregs_from_phi(Is, Pred, Res, Acc#{V=>Reg}); #{Dst:=_} -> res_xregs_from_phi(Is, Pred, Res, Acc) @@ -2138,12 +2272,12 @@ reserve_call_args([], _, Xs) -> Xs. reserve_xreg(V, Xs, Res) -> case Res of #{V:=_} -> - %% Already reserved. + %% Already reserved (but not as an X register). Res; #{} -> case Xs of #{V:=X} -> - %% Add a hint that a specific X register is + %% Add a hint that this specific X register is %% preferred, unless it is already in use. Res#{V=>{prefer,X}}; #{} -> @@ -2152,23 +2286,15 @@ reserve_xreg(V, Xs, Res) -> end end. -is_gc_safe(#b_set{op=phi}) -> - false; -is_gc_safe(#b_set{op=Op,args=Args}) -> - case beam_ssa_codegen:classify_heap_need(Op, Args) of - neutral -> true; - {put,_} -> true; - _ -> false - end. - %% res_xregs_prune(PreferredRegs, Used, Res) -> PreferredRegs. -%% Prune the list of preferred to only include X registers that -%% are guaranteed to survice a garbage collection. +%% Prune the list of preferred registers, to make sure that +%% there are no "holes" (uninitialized X registers) when +%% invoking the garbage collector. -res_xregs_prune(Xs, Used, Res) -> +res_xregs_prune(Xs, Used, Res) when map_size(Xs) =/= 0 -> %% The number of safe registers is the number of the X registers %% used after this point. The actual number of safe registers may - %% be highter than this number, but this is a conservative safe + %% be higher than this number, but this is a conservative safe %% estimate. NumSafe = foldl(fun(V, N) -> case Res of @@ -2180,7 +2306,8 @@ res_xregs_prune(Xs, Used, Res) -> %% Remove unsafe registers from the list of potential %% preferred registers. - maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs). + maps:filter(fun(_, {x,X}) -> X < NumSafe end, Xs); +res_xregs_prune(Xs, _Used, _Res) -> Xs. %%% %%% Register allocation using linear scan. @@ -2486,7 +2613,7 @@ rel2fam(S0) -> sofs:to_external(S). split_phis(Is) -> - partition(fun(#b_set{op=Op}) -> Op =:= phi end, Is). + splitwith(fun(#b_set{op=Op}) -> Op =:= phi end, Is). is_yreg({y,_}) -> true; is_yreg({x,_}) -> false; diff --git a/lib/compiler/src/beam_ssa_share.erl b/lib/compiler/src/beam_ssa_share.erl new file mode 100644 index 0000000000..426efa2cc9 --- /dev/null +++ b/lib/compiler/src/beam_ssa_share.erl @@ -0,0 +1,370 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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% +%% + +%% +%% Share code for semantically equivalent blocks referred to +%% to by `br` and `switch` instructions. +%% +%% A similar optimization is done in beam_jump, but doing it here as +%% well is beneficial as it may enable other optimizations. If there +%% are many semantically equivalent clauses, this optimization can +%% substanstially decrease compilation times. +%% +%% block/2 is called from the liveness optimization pass in +%% beam_ssa_opt, as code sharing helps the liveness pass and vice +%% versa. +%% + +-module(beam_ssa_share). +-export([module/2,block/2]). + +-include("beam_ssa.hrl"). + +-import(lists, [keyfind/3,reverse/1,sort/1]). + +-spec module(beam_ssa:b_module(), [compile:option()]) -> + {'ok',beam_ssa:b_module()}. + +module(#b_module{body=Fs0}=Module, _Opts) -> + Fs = [function(F) || F <- Fs0], + {ok,Module#b_module{body=Fs}}. + +-spec block(Blk0, Blocks0) -> Blk when + Blk0 :: beam_ssa:b_blk(), + Blocks0 :: beam_ssa:block_map(), + Blk :: beam_ssa:b_blk(). + +block(#b_blk{last=Last0}=Blk, Blocks) -> + case share_terminator(Last0, Blocks) of + none -> Blk; + Last -> Blk#b_blk{last=beam_ssa:normalize(Last)} + end. + +%%% +%%% Local functions. +%%% + +function(#b_function{anno=Anno,bs=Blocks0}=F) -> + try + PO = reverse(beam_ssa:rpo(Blocks0)), + {Blocks1,Changed} = blocks(PO, Blocks0, false), + Blocks = case Changed of + true -> + beam_ssa:trim_unreachable(Blocks1); + false -> + Blocks0 + end, + F#b_function{bs=Blocks} + catch + Class:Error:Stack -> + #{func_info:={_,Name,Arity}} = Anno, + io:fwrite("Function: ~w/~w\n", [Name,Arity]), + erlang:raise(Class, Error, Stack) + end. + +blocks([L|Ls], Blocks, Changed) -> + #b_blk{last=Last0} = Blk0 = map_get(L, Blocks), + case block(Blk0, Blocks) of + #b_blk{last=Last0} -> + blocks(Ls, Blocks, Changed); + #b_blk{}=Blk -> + blocks(Ls, Blocks#{L:=Blk}, true) + end; +blocks([], Blocks, Changed) -> + {Blocks,Changed}. + +share_terminator(#b_br{bool=#b_var{},succ=Succ0,fail=Fail0}=Br, Blocks) -> + {Succ,SuccBlk} = shortcut_nonempty_block(Succ0, Blocks), + {Fail,FailBlk} = shortcut_nonempty_block(Fail0, Blocks), + case are_equivalent(Succ, SuccBlk, Fail, FailBlk, Blocks) of + true -> + %% The blocks are semantically equivalent. + Br#b_br{succ=Succ,fail=Succ}; + false -> + if + Succ =:= Succ0, Fail =:= Fail0 -> + %% None of blocks were cut short. + none; + true -> + %% One or both labels were cut short + %% to avoid jumping to an empty block. + Br#b_br{succ=Succ,fail=Fail} + end + end; +share_terminator(#b_switch{}=Sw, Blocks) -> + share_switch(Sw, Blocks); +share_terminator(_Last, _Blocks) -> none. + +%% Test whether the two blocks are semantically equivalent. This +%% function is specially optimized to return `false` as fast as +%% possible if the blocks are not equivalent, as that is the common +%% case. + +are_equivalent(_Succ, _, ?BADARG_BLOCK, _, _Blocks) -> + %% ?BADARG_BLOCK is special. Sharing could be incorrect. + false; +are_equivalent(_Succ, #b_blk{is=Is1,last=#b_ret{arg=RetVal1}=Ret1}, + _Fail, #b_blk{is=Is2,last=#b_ret{arg=RetVal2}=Ret2}, _Blocks) -> + case {RetVal1,RetVal2} of + {#b_literal{},#b_literal{}} -> + case RetVal1 =:= RetVal2 of + true -> + %% The return values are identical literals. We + %% only need to compare the canonicalized bodies. + Can1 = canonical_is(Is1), + Can2 = canonical_is(Is2), + Can1 =:= Can2; + false -> + %% Non-equal literals. + false + end; + {#b_var{},#b_var{}} -> + %% The return values are varibles. We must canonicalize + %% the blocks (including returns) and compare them. + Can1 = canonical_is(Is1 ++ [Ret1]), + Can2 = canonical_is(Is2 ++ [Ret2]), + Can1 =:= Can2; + {_,_} -> + %% One literal and one variable. + false + end; +are_equivalent(Succ, + #b_blk{is=Is1, + last=#b_br{bool=#b_literal{val=true}, + succ=Target}}, + Fail, + #b_blk{is=Is2, + last=#b_br{bool=#b_literal{val=true}, + succ=Target}}, + Blocks) -> + %% Both blocks end with an unconditional branch to the + %% same target block. If the target block has phi nodes, + %% we must pick up the values from the phi nodes and + %% compare them. + #b_blk{is=Is} = map_get(Target, Blocks), + Phis1 = canonical_terminator_phis(Is, Succ), + Phis2 = canonical_terminator_phis(Is, Fail), + case {Phis1,Phis2} of + {[#b_set{args=[#b_literal{}]}|_],_} when Phis1 =/= Phis2 -> + %% Different values are used in the phi nodes. + false; + {_,[#b_set{args=[#b_literal{}]}|_]} when Phis1 =/= Phis2 -> + %% Different values are used in the phi nodes. + false; + {_,_} -> + %% The values in the phi nodes are variables or identical + %% literals. We must canonicalize the blocks and compare + %% them. + Can1 = canonical_is(Is1 ++ Phis1), + Can2 = canonical_is(Is2 ++ Phis2), + Can1 =:= Can2 + end; +are_equivalent(Succ0, #b_blk{is=Is1,last=#b_br{bool=#b_var{},fail=Same}}, + Fail0, #b_blk{is=Is2,last=#b_br{bool=#b_var{},fail=Same}}, + Blocks) -> + %% Two-way branches with identical failure labels. First compare the + %% canonicalized bodies of the blocks. + case canonical_is(Is1) =:= canonical_is(Is2) of + false -> + %% Different bodies. + false; + true -> + %% Bodies were equal. That is fairly uncommon, so to keep + %% the code simple we will rewrite the `br` to a `switch` + %% and let share_switch/2 do the work of following the + %% branches. + Sw = #b_switch{arg=#b_var{name=not_used},fail=Fail0, + list=[{#b_literal{},Succ0}]}, + #b_switch{fail=Fail,list=[{_,Succ}]} = share_switch(Sw, Blocks), + Fail =:= Succ + end; +are_equivalent(_, _, _, _, _) -> false. + +share_switch(#b_switch{fail=Fail0,list=List0}=Sw, Blocks) -> + Prep = share_prepare_sw([{value,Fail0}|List0], Blocks, 0, []), + Res = do_share_switch(Prep, Blocks, []), + [{_,Fail}|List] = [VL || {_,VL} <- sort(Res)], + Sw#b_switch{fail=Fail,list=List}. + +share_prepare_sw([{V,L0}|T], Blocks, N, Acc) -> + {L,_Blk} = shortcut_nonempty_block(L0, Blocks), + share_prepare_sw(T, Blocks, N+1, [{{L,#{}},{N,{V,L}}}|Acc]); +share_prepare_sw([], _, _, Acc) -> Acc. + +do_share_switch(Prep, Blocks, Acc) -> + Map = share_switch_1(Prep, Blocks, #{}), + share_switch_2(maps:values(Map), Blocks, Acc). + +share_switch_1([{Next0,Res}|T], Blocks, Map) -> + {Can,Next} = canonical_block(Next0, Blocks), + case Map of + #{Can:=Ls} -> + share_switch_1(T, Blocks, Map#{Can:=[{Next,Res}|Ls]}); + #{} -> + share_switch_1(T, Blocks, Map#{Can=>[{Next,Res}]}) + end; +share_switch_1([], _Blocks, Map) -> Map. + +share_switch_2([[{_,{N,Res}}]|T], Blocks, Acc) -> + %% This block is not equivalent to any other block. + share_switch_2(T, Blocks, [{N,Res}|Acc]); +share_switch_2([[{done,{_,{_,Common}}}|_]=Eqs|T], Blocks, Acc0) -> + %% Two or more blocks are semantically equivalent, and all blocks + %% are either terminated with a `ret` or a `br` to the same target + %% block. Replace the labels in the `switch` for all of those + %% blocks with the label for the first of the blocks. + Acc = [{N,{V,Common}} || {done,{N,{V,_}}} <- Eqs] ++ Acc0, + share_switch_2(T, Blocks, Acc); +share_switch_2([[{_,_}|_]=Prep|T], Blocks, Acc0) -> + %% Two or more blocks are semantically equivalent, but they have + %% different successful successor blocks. Now we must check + %% recursively whether the successor blocks are equivalent too. + Acc = do_share_switch(Prep, Blocks, Acc0), + share_switch_2(T, Blocks, Acc); +share_switch_2([], _, Acc) -> Acc. + +canonical_block({L,VarMap0}, Blocks) -> + #b_blk{is=Is,last=Last0} = map_get(L, Blocks), + case canonical_terminator(L, Last0, Blocks) of + none -> + %% The block has a terminator that we don't handle. + {{none,L},done}; + {Last,done} -> + %% The block ends with a `ret` or an unconditional `br` to + %% another block. + {Can,_VarMap} = canonical_is(Is ++ Last, VarMap0, []), + {Can,done}; + {Last,Next} -> + %% The block ends with a conditional branch. + {Can,VarMap} = canonical_is(Is ++ Last, VarMap0, []), + {Can,{Next,VarMap}} + end. + +%% Translate a sequence of instructions to a canonical representation. If the +%% canonical representation of two blocks compare equal, the blocks are +%% semantically equivalent. The following translations are done: +%% +%% * Variables defined in the instruction sequence are replaced with +%% {var,0}, {var,1}, and so on. Free variables are not changed. +%% +%% * `location` annotations that would produce a `line` instruction are +%% kept. All other annotations are cleared. +%% +%% * Instructions are repackaged into tuples instead of into the +%% usual records. The main reason is to avoid violating the types for +%% the SSA records. We can simplify things a little by linking the +%% instructions directly instead of putting them into a list. + +canonical_is(Is) -> + {Can,_} = canonical_is(Is, #{}, []), + Can. + +canonical_is([#b_set{op=Op,dst=Dst,args=Args0}=I|Is], VarMap0, Acc) -> + Args = [canonical_arg(Arg, VarMap0) || Arg <-Args0], + Var = {var,map_size(VarMap0)}, + VarMap = VarMap0#{Dst=>Var}, + LineAnno = case Op of + bs_match -> + %% The location annotation for a bs_match instruction + %% is only used in warnings, never to emit a `line` + %% instruction. Therefore, it should not be included. + []; + _ -> + %% The location annotation will be used in a `line` + %% instruction. It must be included. + beam_ssa:get_anno(location, I, none) + end, + canonical_is(Is, VarMap, {Op,LineAnno,Var,Args,Acc}); +canonical_is([#b_ret{arg=Arg}], VarMap, Acc0) -> + Acc1 = case Acc0 of + {call,_Anno,Var,[#b_local{}|_]=Args,PrevAcc} -> + %% This is a tail-recursive call to a local function. + %% There will be no line instruction generated; + %% thus, the annotation is not significant. + {call,[],Var,Args,PrevAcc}; + _ -> + Acc0 + end, + {{ret,canonical_arg(Arg, VarMap),Acc1},VarMap}; +canonical_is([#b_br{bool=#b_var{},fail=Fail}], VarMap, Acc) -> + {{br,succ,Fail,Acc},VarMap}; +canonical_is([#b_br{succ=Succ}], VarMap, Acc) -> + {{br,Succ,Acc},VarMap}; +canonical_is([], VarMap, Acc) -> + {Acc,VarMap}. + +canonical_terminator(_L, #b_ret{}=Ret, _Blocks) -> + {[Ret],done}; +canonical_terminator(L, #b_br{bool=#b_literal{val=true},succ=Succ}=Br, Blocks) -> + #b_blk{is=Is} = map_get(Succ, Blocks), + case canonical_terminator_phis(Is, L) of + [] -> + {[],Succ}; + [_|_]=Phis -> + {Phis ++ [Br],done} + end; +canonical_terminator(_L, #b_br{bool=#b_var{},succ=Succ}=Br, _Blocks) -> + {[Br],Succ}; +canonical_terminator(_, _, _) -> none. + +canonical_terminator_phis([#b_set{op=phi,args=PhiArgs}=Phi|Is], L) -> + {Value,L} = keyfind(L, 2, PhiArgs), + [Phi#b_set{op=copy,args=[Value]}|canonical_terminator_phis(Is, L)]; +canonical_terminator_phis([#b_set{op=peek_message}=I|_], L) -> + %% We could get stuck into an infinite loop if we allowed the + %% comparisons to continue into this block. Force an unequal + %% compare with all other predecessors of this block. + [I#b_set{op=copy,args=[#b_literal{val=L}]}]; +canonical_terminator_phis(_, _) -> []. + +canonical_arg(#b_var{}=Var, VarMap) -> + case VarMap of + #{Var:=CanonicalVar} -> + CanonicalVar; + #{} -> + Var + end; +canonical_arg(#b_remote{mod=Mod,name=Name}, VarMap) -> + {remote,canonical_arg(Mod, VarMap), + canonical_arg(Name, VarMap)}; +canonical_arg(Other, _VarMap) -> Other. + +%% Shortcut branches to empty blocks if safe. + +shortcut_nonempty_block(L, Blocks) -> + case map_get(L, Blocks) of + #b_blk{is=[],last=#b_br{bool=#b_literal{val=true},succ=Succ}}=Blk -> + %% This block is empty. + case is_forbidden(Succ, Blocks) of + false -> + shortcut_nonempty_block(Succ, Blocks); + true -> + {L,Blk} + end; + #b_blk{}=Blk -> + {L,Blk} + end. + +is_forbidden(L, Blocks) -> + case map_get(L, Blocks) of + #b_blk{is=[#b_set{op=phi}|_]} -> true; + #b_blk{is=[#b_set{op=peek_message}|_]} -> true; + #b_blk{} -> false + end. diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index 95fc3bb0e9..38ea5e6914 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -19,19 +19,22 @@ %% -module(beam_ssa_type). --export([opt/2]). +-export([opt_start/4, opt_continue/4, opt_finish/3]). --include("beam_ssa.hrl"). +-include("beam_ssa_opt.hrl"). -import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2, - reverse/1,sort/1]). + partition/2,reverse/1,sort/1]). -define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}). --record(d, {ds :: #{beam_ssa:b_var():=beam_ssa:b_set()}, - ls :: #{beam_ssa:label():=type_db()}, - once :: cerl_sets:set(beam_ssa:b_var()), - sub :: #{beam_ssa:b_var():=beam_ssa:value()} - }). +-record(d, + {ds :: #{beam_ssa:b_var():=beam_ssa:b_set()}, + ls :: #{beam_ssa:label():=type_db()}, + once :: cerl_sets:set(beam_ssa:b_var()), + func_id :: func_id(), + func_db :: func_info_db(), + sub = #{} :: #{beam_ssa:b_var():=beam_ssa:value()}, + ret_type = [] :: [type()]}). -define(ATOM_SET_SIZE, 5). @@ -49,36 +52,155 @@ {'binary',pos_integer()} | 'cons' | 'float' | 'list' | 'map' | 'nil' |'number'. -type type_db() :: #{beam_ssa:var_name():=type()}. --spec opt([{Label0,Block0}], Args) -> [{Label,Block}] when - Label0 :: beam_ssa:label(), - Block0 :: beam_ssa:b_blk(), +-spec opt_start(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when + Linear :: [{non_neg_integer(), beam_ssa:b_blk()}], Args :: [beam_ssa:b_var()], - Label :: beam_ssa:label(), - Block :: beam_ssa:b_blk(). - -opt(Linear, Args) -> - UsedOnce = used_once(Linear, Args), + Anno :: beam_ssa:anno(), + FuncDb :: func_info_db(). +opt_start(Linear, Args, Anno, FuncDb) -> + %% This is the first run through the module, so our arg_types can be + %% incomplete as we may not have visited all call sites at least once. Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]), + opt_continue_1(Linear, Args, get_func_id(Anno), Ts, FuncDb). + +-spec opt_continue(Linear, Args, Anno, FuncDb) -> {Linear, FuncDb} when + Linear :: [{non_neg_integer(), beam_ssa:b_blk()}], + Args :: [beam_ssa:b_var()], + Anno :: beam_ssa:anno(), + FuncDb :: func_info_db(). +opt_continue(Linear, Args, Anno, FuncDb) -> + Id = get_func_id(Anno), + case FuncDb of + #{ Id := #func_info{exported=false,arg_types=ArgTypes} } -> + %% This is a local function and we're guaranteed to have visited + %% every call site at least once, so we know that the parameter + %% types are at least as narrow as the join of all argument types. + Ts = join_arg_types(Args, ArgTypes, Anno), + opt_continue_1(Linear, Args, Id, Ts, FuncDb); + #{} -> + %% We can't infer the parameter types of exported functions, nor + %% the ones where module-level optimization is disabled, but + %% running the pass again could still help other functions. + Ts = maps:from_list([{V,any} || #b_var{}=V <- Args]), + opt_continue_1(Linear, Args, Id, Ts, FuncDb) + end. + +join_arg_types(Args, ArgTypes, Anno) -> + %% We suppress type optimization for parameters that have already been + %% optimized by another pass, as they may have done things we have no idea + %% how to interpret and running them over could generate incorrect code. + ParamTypes = maps:get(parameter_type_info, Anno, #{}), + Ts0 = join_arg_types_1(Args, ArgTypes, #{}), + maps:fold(fun(Arg, _V, Ts) -> + maps:put(Arg, any, Ts) + end, Ts0, ParamTypes). + +join_arg_types_1([Arg | Args], [TM | TMs], Ts) when map_size(TM) =/= 0 -> + join_arg_types_1(Args, TMs, Ts#{ Arg => join(maps:values(TM))}); +join_arg_types_1([Arg | Args], [_TM | TMs], Ts) -> + join_arg_types_1(Args, TMs, Ts#{ Arg => any }); +join_arg_types_1([], [], Ts) -> + Ts. + +-spec opt_continue_1(Linear, Args, Id, Ts, FuncDb) -> Result when + Linear :: [{non_neg_integer(), beam_ssa:b_blk()}], + Args :: [beam_ssa:b_var()], + Id :: func_id(), + Ts :: type_db(), + FuncDb :: func_info_db(), + Result :: {Linear, FuncDb}. +opt_continue_1(Linear0, Args, Id, Ts, FuncDb0) -> + UsedOnce = used_once(Linear0, Args), FakeCall = #b_set{op=call,args=[#b_remote{mod=#b_literal{val=unknown}, name=#b_literal{val=unknown}, arity=0}]}, Defs = maps:from_list([{Var,FakeCall#b_set{dst=Var}} || #b_var{}=Var <- Args]), - D = #d{ds=Defs,ls=#{0=>Ts,?BADARG_BLOCK=>#{}}, - once=UsedOnce,sub=#{}}, - opt_1(Linear, D). -opt_1([{L,Blk}|Bs], #d{ls=Ls}=D) -> + D = #d{ func_db=FuncDb0, + func_id=Id, + ds=Defs, + ls=#{0=>Ts,?BADARG_BLOCK=>#{}}, + once=UsedOnce }, + + {Linear, FuncDb, NewRet} = opt_1(Linear0, D, []), + + case FuncDb of + #{ Id := Entry0 } -> + Entry = Entry0#func_info{ret_type=NewRet}, + {Linear, FuncDb#{ Id := Entry }}; + #{} -> + %% Module-level optimizations have been turned off for this + %% function. + {Linear, FuncDb} + end. + +-spec opt_finish(Args, Anno, FuncDb) -> {Anno, FuncDb} when + Args :: [beam_ssa:b_var()], + Anno :: beam_ssa:anno(), + FuncDb :: func_info_db(). +opt_finish(Args, Anno, FuncDb) -> + Id = get_func_id(Anno), + case FuncDb of + #{ Id := #func_info{exported=false,arg_types=ArgTypes} } -> + ParamInfo0 = maps:get(parameter_type_info, Anno, #{}), + ParamInfo = opt_finish_1(Args, ArgTypes, ParamInfo0), + {Anno#{ parameter_type_info => ParamInfo }, FuncDb}; + #{} -> + {Anno, FuncDb} + end. + +opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo) + when is_map_key(Arg, ParamInfo); %% See join_arg_types/3 + map_size(TypeMap) =:= 0 -> + opt_finish_1(Args, TypeMaps, ParamInfo); +opt_finish_1([Arg | Args], [TypeMap | TypeMaps], ParamInfo0) -> + case join(maps:values(TypeMap)) of + any -> + opt_finish_1(Args, TypeMaps, ParamInfo0); + JoinedType -> + JoinedType = verified_type(JoinedType), + ParamInfo = ParamInfo0#{ Arg => validator_anno(JoinedType) }, + opt_finish_1(Args, TypeMaps, ParamInfo) + end; +opt_finish_1([], [], ParamInfo) -> + ParamInfo. + +validator_anno(#t_tuple{size=Size,exact=Exact}) -> + beam_validator:type_anno(tuple, Size, Exact); +validator_anno(#t_integer{elements={Same,Same}}) -> + beam_validator:type_anno(integer, Same); +validator_anno(#t_integer{}) -> + beam_validator:type_anno(integer); +validator_anno(float) -> + beam_validator:type_anno(float); +validator_anno(#t_atom{elements=[Val]}) -> + beam_validator:type_anno(atom, Val); +validator_anno(#t_atom{}=A) -> + case t_is_boolean(A) of + true -> beam_validator:type_anno(bool); + false -> beam_validator:type_anno(atom) + end; +validator_anno(T) -> + beam_validator:type_anno(T). + +get_func_id(Anno) -> + #{func_info:={_Mod, Name, Arity}} = Anno, + #b_local{name=#b_literal{val=Name}, arity=Arity}. + +opt_1([{L,Blk}|Bs], #d{ls=Ls}=D, Acc) -> case Ls of #{L:=Ts} -> - opt_2(L, Blk, Bs, Ts, D); + opt_2(L, Blk, Bs, Ts, D, Acc); #{} -> %% This block is never reached. Discard it. - opt_1(Bs, D) + opt_1(Bs, D, Acc) end; -opt_1([], #d{}) -> []. +opt_1([], D, Acc) -> + #d{func_db=FuncDb,ret_type=NewRet} = D, + {reverse(Acc), FuncDb, NewRet}. -opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, #d{sub=Sub}=D0) -> +opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, #d{sub=Sub}=D0, Acc) -> case Is0 of [#b_set{op=call,dst=Dst, args=[#b_remote{mod=#b_literal{val=Mod}, @@ -94,34 +216,43 @@ opt_2(L, #b_blk{is=Is0}=Blk0, Bs, Ts, #d{sub=Sub}=D0) -> Ret = #b_ret{arg=Dst}, Blk = Blk0#b_blk{is=[I],last=Ret}, Ls = maps:remove(L, D0#d.ls), - D = D0#d{ls=Ls}, - [{L,Blk}|opt_1(Bs, D)]; + + %% We potentially lack a return value. + RetType = join([none | D0#d.ret_type]), + + D = D0#d{ls=Ls,ret_type=[RetType]}, + opt_1(Bs, D, [{L,Blk} | Acc]); false -> - opt_3(L, Blk0, Bs, Ts, D0) + opt_3(L, Blk0, Bs, Ts, D0, Acc) end; _ -> - opt_3(L, Blk0, Bs, Ts, D0) + opt_3(L, Blk0, Bs, Ts, D0, Acc) end. opt_3(L, #b_blk{is=Is0,last=Last0}=Blk0, Bs, Ts0, - #d{ds=Ds0,ls=Ls0,sub=Sub0}=D0) -> - {Is,Ts,Ds,Sub} = opt_is(Is0, Ts0, Ds0, Ls0, Sub0, []), - D1 = D0#d{ds=Ds,sub=Sub}, - Last1 = simplify_terminator(Last0, Sub, Ts), + #d{ds=Ds0,ls=Ls0,sub=Sub0,func_db=Fdb0}=D0, Acc) -> + {Is,Ts,Ds,Fdb,Sub} = opt_is(Is0, Ts0, Ds0, Fdb0, Ls0, D0, Sub0, []), + D1 = D0#d{ds=Ds,sub=Sub,func_db=Fdb}, + Last1 = simplify_terminator(Last0, Sub, Ts, Ds), Last = opt_terminator(Last1, Ts, Ds), D = update_successors(Last, Ts, D1), Blk = Blk0#b_blk{is=Is,last=Last}, - [{L,Blk}|opt_1(Bs, D)]. + opt_1(Bs, D, [{L,Blk} | Acc]). -simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts) -> +simplify_terminator(#b_br{bool=Bool}=Br, Sub, Ts, _Ds) -> Br#b_br{bool=simplify_arg(Bool, Sub, Ts)}; -simplify_terminator(#b_switch{arg=Arg}=Sw, Sub, Ts) -> +simplify_terminator(#b_switch{arg=Arg}=Sw, Sub, Ts, _Ds) -> Sw#b_switch{arg=simplify_arg(Arg, Sub, Ts)}; -simplify_terminator(#b_ret{arg=Arg}=Ret, Sub, Ts) -> - Ret#b_ret{arg=simplify_arg(Arg, Sub, Ts)}. +simplify_terminator(#b_ret{arg=Arg}=Ret, Sub, Ts, Ds) -> + %% Reducing the result of a call to a literal (fairly common for 'ok') + %% breaks tail call optimization. + case Ds of + #{ Arg := #b_set{op=call}} -> Ret; + #{} -> Ret#b_ret{arg=simplify_arg(Arg, Sub, Ts)} + end. opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is], - Ts0, Ds0, Ls, Sub0, Acc) -> + Ts0, Ds0, Fdb, Ls, D, Sub0, Acc) -> %% Simplify the phi node by removing all predecessor blocks that no %% longer exists or no longer branches to this block. Args = [{simplify_arg(Arg, Sub0, Ts0),From} || @@ -132,15 +263,61 @@ opt_is([#b_set{op=phi,dst=Dst,args=Args0}=I0|Is], %% value or if the values are identical. [{Val,_}|_] = Args, Sub = Sub0#{Dst=>Val}, - opt_is(Is, Ts0, Ds0, Ls, Sub, Acc); + opt_is(Is, Ts0, Ds0, Fdb, Ls, D, Sub, Acc); false -> I = I0#b_set{args=Args}, Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{Dst=>I}, - opt_is(Is, Ts, Ds, Ls, Sub0, [I|Acc]) + opt_is(Is, Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc]) + end; +opt_is([#b_set{op=call,args=Args0,dst=Dst}=I0 | Is], + Ts0, Ds0, Fdb0, Ls, D, Sub, Acc) -> + Args = simplify_args(Args0, Sub, Ts0), + I1 = beam_ssa:normalize(I0#b_set{args=Args}), + + %% This is a bit of a kludge; we know that any instruction whose return + %% type is 'none' will fail at runtime, but we don't yet have a way to cut + %% a block short so we move on like nothing nothing happened. + %% + %% This complicates argument type optimization as unreachable calls can + %% add types that will never occur, so we skip optimizing this call if + %% the type of any of its arguments is 'none'. + [_Callee | Rest] = Args, + case all(fun(Arg) -> get_type(Arg, Ts0) =/= none end, Rest) of + true -> + {Ts, Ds, Fdb, I} = opt_call(I1, D, Ts0, Ds0, Fdb0), + opt_is(Is, Ts, Ds, Fdb, Ls, D, Sub, [I|Acc]); + false -> + Ts = Ts0#{ Dst => any }, + Ds = Ds0#{ Dst => I1 }, + opt_is(Is, Ts, Ds, Fdb0, Ls, D, Sub, [I1|Acc]) + end; +opt_is([#b_set{op=succeeded,args=[Arg],dst=Dst}=I], + Ts0, Ds0, Fdb, Ls, D, Sub0, Acc) -> + case Ds0 of + #{ Arg := #b_set{op=call} } -> + %% The success check of a call is part of exception handling and + %% must not be optimized away. We still have to update its type + %% though. + Ts = update_types(I, Ts0, Ds0), + Ds = Ds0#{Dst=>I}, + + opt_is([], Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc]); + #{} -> + Args = simplify_args([Arg], Sub0, Ts0), + Type = type(succeeded, Args, Ts0, Ds0), + case get_literal_from_type(Type) of + #b_literal{}=Lit -> + Sub = Sub0#{Dst=>Lit}, + opt_is([], Ts0, Ds0, Fdb, Ls, D, Sub, Acc); + none -> + Ts = Ts0#{Dst=>Type}, + Ds = Ds0#{Dst=>I}, + opt_is([], Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc]) + end end; opt_is([#b_set{args=Args0,dst=Dst}=I0|Is], - Ts0, Ds0, Ls, Sub0, Acc) -> + Ts0, Ds0, Fdb, Ls, D, Sub0, Acc) -> Args = simplify_args(Args0, Sub0, Ts0), I1 = beam_ssa:normalize(I0#b_set{args=Args}), case simplify(I1, Ts0) of @@ -148,16 +325,76 @@ opt_is([#b_set{args=Args0,dst=Dst}=I0|Is], I = beam_ssa:normalize(I2), Ts = update_types(I, Ts0, Ds0), Ds = Ds0#{Dst=>I}, - opt_is(Is, Ts, Ds, Ls, Sub0, [I|Acc]); + opt_is(Is, Ts, Ds, Fdb, Ls, D, Sub0, [I|Acc]); #b_literal{}=Lit -> Sub = Sub0#{Dst=>Lit}, - opt_is(Is, Ts0, Ds0, Ls, Sub, Acc); + opt_is(Is, Ts0, Ds0, Fdb, Ls, D, Sub, Acc); #b_var{}=Var -> - Sub = Sub0#{Dst=>Var}, - opt_is(Is, Ts0, Ds0, Ls, Sub, Acc) + case Is of + [#b_set{op=succeeded,dst=SuccDst,args=[Dst]}] -> + %% We must remove this 'succeeded' instruction. + Sub = Sub0#{Dst=>Var,SuccDst=>#b_literal{val=true}}, + opt_is([], Ts0, Ds0, Fdb, Ls, D, Sub, Acc); + _ -> + Sub = Sub0#{Dst=>Var}, + opt_is(Is, Ts0, Ds0, Fdb, Ls, D, Sub, Acc) + end end; -opt_is([], Ts, Ds, _Ls, Sub, Acc) -> - {reverse(Acc),Ts,Ds,Sub}. +opt_is([], Ts, Ds, Fdb, _Ls, _D, Sub, Acc) -> + {reverse(Acc), Ts, Ds, Fdb, Sub}. + +opt_call(#b_set{dst=Dst,args=[#b_local{}=Callee|Args]}=I0, D, Ts0, Ds0, Fdb0) -> + {Ts, Ds, I} = opt_local_call(I0, Ts0, Ds0, Fdb0), + case Fdb0 of + #{ Callee := #func_info{exported=false,arg_types=ArgTypes0}=Info } -> + %% Update the argument types of *this exact call*, the types + %% will be joined later when the callee is optimized. + CallId = {D#d.func_id, Dst}, + ArgTypes = update_arg_types(Args, ArgTypes0, CallId, Ts0), + + Fdb = Fdb0#{ Callee => Info#func_info{arg_types=ArgTypes} }, + {Ts, Ds, Fdb, I}; + #{} -> + %% We can't narrow the argument types of exported functions as they + %% can receive anything as part of an external call. + {Ts, Ds, Fdb0, I} + end; +opt_call(#b_set{dst=Dst}=I, _D, Ts0, Ds0, Fdb) -> + Ts = update_types(I, Ts0, Ds0), + Ds = Ds0#{ Dst => I }, + {Ts, Ds, Fdb, I}. + +opt_local_call(#b_set{dst=Dst,args=[Id|_]}=I0, Ts0, Ds0, Fdb) -> + %% We skip propagating 'none' as we don't yet have a good way to cut a + %% block short. + Type = case Fdb of + #{ Id := #func_info{ret_type=[T]} } when T =/= none -> T; + #{} -> any + end, + I = case Type of + any -> I0; + _ -> beam_ssa:add_anno(result_type, validator_anno(Type), I0) + end, + Ts = Ts0#{ Dst => Type }, + Ds = Ds0#{ Dst => I }, + {Ts, Ds, I}. + +update_arg_types([Arg | Args], [TypeMap0 | TypeMaps], CallId, Ts) -> + %% Match contexts are treated as bitstrings when optimizing arguments, as + %% we don't yet support removing the "bs_start_match3" instruction. + NewType = case get_type(Arg, Ts) of + #t_bs_match{} -> {binary, 1}; + Type -> Type + end, + PrevType = maps:get(CallId, TypeMap0, NewType), + + %% The new type must be narrower than the old one. + true = meet(NewType, PrevType) =/= none, %Assertion. + + TypeMap = TypeMap0#{ CallId => NewType }, + [TypeMap | update_arg_types(Args, TypeMaps, CallId, Ts)]; +update_arg_types([], [], _CallId, _Ts) -> + []. simplify(#b_set{op={bif,'and'},args=Args}=I, Ts) -> case is_safe_bool_op(Args, Ts) of @@ -289,7 +526,7 @@ simplify(#b_set{op=put_tuple,args=Args}=I, _Ts) -> none -> I; List -> #b_literal{val=list_to_tuple(List)} end; -simplify(#b_set{op=succeeded,args=[#b_literal{}]}, _Ts) -> +simplify(#b_set{op=wait_timeout,args=[#b_literal{val=0}]}, _Ts) -> #b_literal{val=true}; simplify(#b_set{op=wait_timeout,args=[#b_literal{val=infinity}]}=I, _Ts) -> I#b_set{op=wait,args=[]}; @@ -436,12 +673,12 @@ update_successors(#b_br{bool=#b_var{}=Bool,succ=Succ,fail=Fail}, Ts0, D0) -> %% no need to include the type database passed on to the %% successors of this block. Ts = maps:remove(Bool, Ts0), - D = update_successor(Fail, Ts, D0), - SuccTs = infer_types(Bool, Ts, D0), + {SuccTs,FailTs} = infer_types(Bool, Ts, D0), + D = update_successor(Fail, FailTs, D0), update_successor(Succ, SuccTs, D); false -> - D = update_successor_bool(Bool, false, Fail, Ts0, D0), - SuccTs = infer_types(Bool, Ts0, D0), + {SuccTs,FailTs} = infer_types(Bool, Ts0, D0), + D = update_successor_bool(Bool, false, Fail, FailTs, D0), update_successor_bool(Bool, true, Succ, SuccTs, D) end; update_successors(#b_switch{arg=#b_var{}=V,fail=Fail,list=List}, Ts0, D0) -> @@ -458,14 +695,36 @@ update_successors(#b_switch{arg=#b_var{}=V,fail=Fail,list=List}, Ts0, D0) -> end, foldl(F, D, List); false -> - D = update_successor(Fail, Ts0, D0), + %% V can not be equal to any of the values in List at the fail + %% block. + FailTs = subtract_sw_list(V, List, Ts0), + D = update_successor(Fail, FailTs, D0), F = fun({Val,S}, A) -> T = get_type(Val, Ts0), update_successor(S, Ts0#{V=>T}, A) end, foldl(F, D, List) - end; -update_successors(#b_ret{}, _Ts, D) -> D. + end; +update_successors(#b_ret{arg=Arg}, Ts, D) -> + FuncId = D#d.func_id, + case D#d.ds of + #{ Arg := #b_set{op=call,args=[FuncId | _]} } -> + %% Returning a call to ourselves doesn't affect our own return + %% type. + D; + #{} -> + RetType = join([get_type(Arg, Ts) | D#d.ret_type]), + D#d{ret_type=[RetType]} + end. + +subtract_sw_list(V, List, Ts) -> + Ts#{ V := sub_sw_list_1(get_type(V, Ts), List, Ts) }. + +sub_sw_list_1(Type, [{Val,_}|T], Ts) -> + ValType = get_type(Val, Ts), + sub_sw_list_1(subtract(Type, ValType), T, Ts); +sub_sw_list_1(Type, [], _Ts) -> + Type. update_successor_bool(#b_var{}=Var, BoolValue, S, Ts, D) -> case t_is_boolean(get_type(Var, Ts)) of @@ -542,6 +801,14 @@ type(call, [#b_remote{mod=#b_literal{val=Mod}, {_,_} -> #t_tuple{} end; + {erlang,'++',[List1,List2]} -> + case get_type(List1, Ts) =:= cons orelse + get_type(List2, Ts) =:= cons of + true -> cons; + false -> list + end; + {erlang,'--',[_,_]} -> + list; {math,_,_} -> case is_math_bif(Name, length(Args)) of false -> any; @@ -607,6 +874,8 @@ type(succeeded, [#b_var{}=Src], Ts, Ds) -> #b_set{} -> t_boolean() end; +type(succeeded, [#b_literal{}], _Ts, _Ds) -> + t_atom(true); type(_, _, _, _) -> any. arith_op_type(Args, Ts) -> @@ -873,10 +1142,84 @@ get_type(#b_literal{val=Val}, _Ts) -> any end. -infer_types(#b_var{}=V, Ts, #d{ds=Ds}) -> +%% infer_types(Var, Types, #d{}) -> {SuccTypes,FailTypes} +%% Looking at the expression that defines the variable Var, infer +%% the types for the variables in the arguments. Return the updated +%% type database for the case that the expression evaluates to +%% true, and and for the case that it evaluates to false. +%% +%% Here is an example. The variable being asked about is +%% the variable Bool, which is defined like this: +%% +%% Bool = is_nonempty_list L +%% +%% If 'is_nonempty_list L' evaluates to 'true', L must +%% must be cons. The meet of the previously known type of L and 'cons' +%% will be added to SuccTypes. +%% +%% On the other hand, if 'is_nonempty_list L' evaluates to false, L +%% is not cons and cons can be subtracted from the previously known +%% type for L. For example, if L was known to be 'list', subtracting +%% 'cons' would give 'nil' as the only possible type. The result of the +%% subtraction for L will be added to FailTypes. +%% +%% Here is another example, asking about the variable Bool: +%% +%% Head = bif:hd L +%% Bool = succeeded Head +%% +%% 'succeeded Head' will evaluate to 'true' if the instrution that +%% defined Head succeeded. In this case, it is the 'bif:hd L' +%% instruction, which will succeed if L is 'cons'. Thus, the meet of +%% the previous type for L and 'cons' will be added to SuccTypes. +%% +%% If 'succeeded Head' evaluates to 'false', it means that 'bif:hd L' +%% failed and that L is not 'cons'. 'cons' can be subtracted from the +%% previously known type for L and the result put in FailTypes. + +infer_types(#b_var{}=V, Ts, #d{ds=Ds,once=Once}) -> #{V:=#b_set{op=Op,args=Args}} = Ds, - Types = infer_type(Op, Args, Ds), - meet_types(Types, Ts). + Types0 = infer_type(Op, Args, Ds), + + %% We must be careful with types inferred from '=:='. + %% + %% If we have seen L =:= [a], we know that L is 'cons' if the + %% comparison succeeds. However, if the comparison fails, L could + %% still be 'cons'. Therefore, we must not subtract 'cons' from the + %% previous type of L. + %% + %% However, it is safe to subtract a type inferred from '=:=' if + %% it is single-valued, e.g. if it is [] or the atom 'true'. + EqTypes0 = infer_eq_type(Op, Args, Ts, Ds), + {Types1,EqTypes} = partition(fun({_,T}) -> + is_singleton_type(T) + end, EqTypes0), + + %% Don't bother updating the types for variables that + %% are never used again. + Types2 = Types1 ++ Types0, + Types = [P || {InfV,_}=P <- Types2, not cerl_sets:is_element(InfV, Once)], + + {meet_types(EqTypes++Types, Ts),subtract_types(Types, Ts)}. + +infer_eq_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ts, Ds) -> + Def = maps:get(Src, Ds), + Type = get_type(Lit, Ts), + [{Src,Type}|infer_tuple_size(Def, Lit) ++ + infer_first_element(Def, Lit)]; +infer_eq_type({bif,'=:='}, [#b_var{}=Arg0,#b_var{}=Arg1], Ts, _Ds) -> + %% As an example, assume that L1 is known to be 'list', and L2 is + %% known to be 'cons'. Then if 'L1 =:= L2' evaluates to 'true', it can + %% be inferred that L1 is 'cons' (the meet of 'cons' and 'list'). + Type0 = get_type(Arg0, Ts), + Type1 = get_type(Arg1, Ts), + Type = meet(Type0, Type1), + [{V,MeetType} || + {V,OrigType,MeetType} <- + [{Arg0,Type0,Type},{Arg1,Type1,Type}], + OrigType =/= MeetType]; +infer_eq_type(_Op, _Args, _Ts, _Ds) -> + []. infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) -> if @@ -885,20 +1228,27 @@ infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) -> true -> [] end; -infer_type({bif,'=:='}, [#b_var{}=Src,#b_literal{}=Lit], Ds) -> - Def = maps:get(Src, Ds), - Type = get_type(Lit, #{}), - [{Src,Type}|infer_tuple_size(Def, Lit) ++ - infer_first_element(Def, Lit)]; +infer_type({bif,element}, [#b_var{}=Position,#b_var{}=Tuple], _Ds) -> + [{Position,t_integer()},{Tuple,#t_tuple{}}]; infer_type({bif,Bif}, [#b_var{}=Src]=Args, _Ds) -> case inferred_bif_type(Bif, Args) of any -> []; T -> [{Src,T}] end; +infer_type({bif,binary_part}, [#b_var{}=Src,_], _Ds) -> + [{Src,{binary,8}}]; infer_type({bif,is_map_key}, [_,#b_var{}=Src], _Ds) -> [{Src,map}]; infer_type({bif,map_get}, [_,#b_var{}=Src], _Ds) -> [{Src,map}]; +infer_type({bif,Bif}, [_,_]=Args, _Ds) -> + case inferred_bif_type(Bif, Args) of + any -> []; + T -> [{A,T} || #b_var{}=A <- Args] + end; +infer_type({bif,binary_part}, [#b_var{}=Src,Pos,Len], _Ds) -> + [{Src,{binary,8}}| + [{V,t_integer()} || #b_var{}=V <- [Pos,Len]]]; infer_type(bs_start_match, [#b_var{}=Bin], _Ds) -> [{Bin,{binary,1}}]; infer_type(is_nonempty_list, [#b_var{}=Src], _Ds) -> @@ -969,6 +1319,7 @@ inferred_bif_type(is_number, [_]) -> number; inferred_bif_type(is_tuple, [_]) -> #t_tuple{}; inferred_bif_type(abs, [_]) -> number; inferred_bif_type(bit_size, [_]) -> {binary,1}; +inferred_bif_type('bnot', [_]) -> t_integer(); inferred_bif_type(byte_size, [_]) -> {binary,1}; inferred_bif_type(ceil, [_]) -> number; inferred_bif_type(float, [_]) -> number; @@ -976,10 +1327,25 @@ inferred_bif_type(floor, [_]) -> number; inferred_bif_type(hd, [_]) -> cons; inferred_bif_type(length, [_]) -> list; inferred_bif_type(map_size, [_]) -> map; +inferred_bif_type('not', [_]) -> t_boolean(); inferred_bif_type(round, [_]) -> number; inferred_bif_type(trunc, [_]) -> number; inferred_bif_type(tl, [_]) -> cons; inferred_bif_type(tuple_size, [_]) -> #t_tuple{}; +inferred_bif_type('and', [_,_]) -> t_boolean(); +inferred_bif_type('or', [_,_]) -> t_boolean(); +inferred_bif_type('xor', [_,_]) -> t_boolean(); +inferred_bif_type('band', [_,_]) -> t_integer(); +inferred_bif_type('bor', [_,_]) -> t_integer(); +inferred_bif_type('bsl', [_,_]) -> t_integer(); +inferred_bif_type('bsr', [_,_]) -> t_integer(); +inferred_bif_type('bxor', [_,_]) -> t_integer(); +inferred_bif_type('div', [_,_]) -> t_integer(); +inferred_bif_type('rem', [_,_]) -> t_integer(); +inferred_bif_type('+', [_,_]) -> number; +inferred_bif_type('-', [_,_]) -> number; +inferred_bif_type('*', [_,_]) -> number; +inferred_bif_type('/', [_,_]) -> number; inferred_bif_type(_, _) -> any. infer_tuple_size(#b_set{op={bif,tuple_size},args=[#b_var{}=Tuple]}, @@ -1088,6 +1454,9 @@ t_tuple_size(#t_tuple{size=Size,exact=true}) -> t_tuple_size(_) -> none. +is_singleton_type(Type) -> + get_literal_from_type(Type) =/= none. + %% join(Type1, Type2) -> Type %% Return the "join" of Type1 and Type2. The join is a more general %% type than Type1 and Type2. For example: @@ -1152,14 +1521,40 @@ gcd(A, B) -> meet_types([{V,T0}|Vs], Ts) -> #{V:=T1} = Ts, - T = meet(T0, T1), - meet_types(Vs, Ts#{V:=T}); + case meet(T0, T1) of + T1 -> meet_types(Vs, Ts); + T -> meet_types(Vs, Ts#{V:=T}) + end; meet_types([], Ts) -> Ts. meet([T1,T2|Ts]) -> meet([meet(T1, T2)|Ts]); meet([T]) -> T. +subtract_types([{V,T0}|Vs], Ts) -> + #{V:=T1} = Ts, + case subtract(T1, T0) of + T1 -> subtract_types(Vs, Ts); + T -> subtract_types(Vs, Ts#{V:=T}) + end; +subtract_types([], Ts) -> Ts. + +%% subtract(Type1, Type2) -> Type. +%% Subtract Type2 from Type1. Example: +%% +%% subtract(list, cons) -> nil + +subtract(#t_atom{elements=[_|_]=Set0}, #t_atom{elements=[_|_]=Set1}) -> + case ordsets:subtract(Set0, Set1) of + [] -> none; + [_|_]=Set -> #t_atom{elements=Set} + end; +subtract(number, float) -> #t_integer{}; +subtract(number, #t_integer{elements=any}) -> float; +subtract(list, cons) -> nil; +subtract(list, nil) -> cons; +subtract(T, _) -> T. + %% meet(Type1, Type2) -> Type %% Return the "meet" of Type1 and Type2. The meet is a narrower %% type than Type1 and Type2. For example: diff --git a/lib/compiler/src/beam_trim.erl b/lib/compiler/src/beam_trim.erl index 51ff580a7a..acf3838da4 100644 --- a/lib/compiler/src/beam_trim.erl +++ b/lib/compiler/src/beam_trim.erl @@ -200,6 +200,8 @@ create_map(Trim, Moves) -> (Any) -> Any end. +remap([{'%',_}=I|Is], Map, Acc) -> + remap(Is, Map, [I|Acc]); remap([{block,Bl0}|Is], Map, Acc) -> Bl = remap_block(Bl0, Map, []), remap(Is, Map, [{block,Bl}|Acc]); @@ -279,6 +281,8 @@ safe_labels([_|Is], Acc) -> safe_labels(Is, Acc); safe_labels([], Acc) -> cerl_sets:from_list(Acc). +is_safe_label([{'%',_}|Is]) -> + is_safe_label(Is); is_safe_label([{line,_}|Is]) -> is_safe_label(Is); is_safe_label([{badmatch,{Tag,_}}|_]) -> @@ -337,6 +341,8 @@ frame_layout_2(Is) -> reverse(Is). %% to safe labels (i.e., the code at those labels don't depend %% on the contents of any Y register). +frame_size([{'%',_}|Is], Safe) -> + frame_size(Is, Safe); frame_size([{block,_}|Is], Safe) -> frame_size(Is, Safe); frame_size([{call_fun,_}|Is], Safe) -> @@ -393,6 +399,8 @@ frame_size_branch(L, Is, Safe) -> %% This function handles the same instructions as frame_size/2. It %% assumes that any labels in the instructions are safe labels. +is_not_used(Y, [{'%',_}|Is]) -> + is_not_used(Y, Is); is_not_used(Y, [{apply,_}|Is]) -> is_not_used(Y, Is); is_not_used(Y, [{bif,_,{f,_},Ss,Dst}|Is]) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 7d908df3bf..4081e366a5 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -26,6 +26,7 @@ %% Interface for compiler. -export([module/2, format_error/1]). +-export([type_anno/1, type_anno/2, type_anno/3]). -import(lists, [any/2,dropwhile/2,foldl/3,map/2,foreach/2,reverse/1]). @@ -44,6 +45,33 @@ module({Mod,Exp,Attr,Fs,Lc}=Code, _Opts) {error,[{atom_to_list(Mod),Es}]} end. +%% Provides a stable interface for type annotations, used by certain passes to +%% indicate that we can safely assume that a register has a given type. +-spec type_anno(term()) -> term(). +type_anno(atom) -> {atom,[]}; +type_anno(bool) -> bool; +type_anno({binary,_}) -> term; +type_anno(cons) -> cons; +type_anno(float) -> {float,[]}; +type_anno(integer) -> {integer,[]}; +type_anno(list) -> list; +type_anno(map) -> map; +type_anno(match_context) -> match_context; +type_anno(number) -> number; +type_anno(nil) -> nil. + +-spec type_anno(term(), term()) -> term(). +type_anno(atom, Value) -> {atom, Value}; +type_anno(float, Value) -> {float, Value}; +type_anno(integer, Value) -> {integer, Value}. + +-spec type_anno(term(), term(), term()) -> term(). +type_anno(tuple, Size, Exact) when is_integer(Size) -> + case Exact of + true -> {tuple, Size}; + false -> {tuple, [Size]} + end. + -spec format_error(term()) -> iolist(). format_error({{_M,F,A},{I,Off,limit}}) -> @@ -93,28 +121,6 @@ validate(Module, Fs) -> Ft = index_parameter_types(Fs, []), validate_0(Module, Fs, Ft). -index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) -> - Code = dropwhile(fun({label,L}) when L =:= Entry -> false; - (_) -> true - end, Code0), - case Code of - [{label,Entry}|Is] -> - Acc = index_parameter_types_1(Is, Entry, Acc0), - index_parameter_types(Fs, Acc); - _ -> - %% Something serious is wrong. Ignore it for now. - %% It will be detected and diagnosed later. - index_parameter_types(Fs, Acc0) - end; -index_parameter_types([], Acc) -> - gb_trees:from_orddict(lists:sort(Acc)). - -index_parameter_types_1([{'%', {type_info, Reg, Type}} | Is], Entry, Acc) -> - Key = {Entry, Reg}, - index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]); -index_parameter_types_1(_, _, Acc) -> - Acc. - validate_0(_Module, [], _) -> []; validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) -> try validate_1(Code, Name, Ar, Entry, Ft) of @@ -167,6 +173,32 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) -> slots=0 :: non_neg_integer() %Number of slots }). +index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) -> + Code = dropwhile(fun({label,L}) when L =:= Entry -> false; + (_) -> true + end, Code0), + case Code of + [{label,Entry}|Is] -> + Acc = index_parameter_types_1(Is, Entry, Acc0), + index_parameter_types(Fs, Acc); + _ -> + %% Something serious is wrong. Ignore it for now. + %% It will be detected and diagnosed later. + index_parameter_types(Fs, Acc0) + end; +index_parameter_types([], Acc) -> + gb_trees:from_orddict(lists:sort(Acc)). + +index_parameter_types_1([{'%', {type_info, Reg, Type0}} | Is], Entry, Acc) -> + Type = case Type0 of + match_context -> #ms{}; + _ -> Type0 + end, + Key = {Entry, Reg}, + index_parameter_types_1(Is, Entry, [{Key, Type} | Acc]); +index_parameter_types_1(_, _, Acc) -> + Acc. + validate_1(Is, Name, Arity, Entry, Ft) -> validate_2(labels(Is), Name, Arity, Entry, Ft). @@ -386,25 +418,19 @@ valfun_1(remove_message, Vst) -> %% The message term is no longer fragile. It can be used %% without restrictions. remove_fragility(Vst); -valfun_1({'%', {type_info, Reg, Info0}}, Vst0) -> +valfun_1({'%', {type_info, Reg, match_context}}, Vst0) -> + set_aliased_type(#ms{}, Reg, Vst0); +valfun_1({'%', {type_info, Reg, NewType0}}, Vst0) -> %% Explicit type information inserted by optimization passes to indicate %% that Reg has a certain type, so that we can accept cross-function type %% optimizations. - %% - %% At the moment we only allow this when narrowing from 'term' which is - %% what to expect with function parameters, but in theory any narrowing - %% conversion should be legal. - case get_move_term_type(Reg, Vst0) of - term -> - Type0 = case Info0 of - match_context -> #ms{}; - _ -> Info0 - end, - Type = propagate_fragility(Type0, [Reg], Vst0), - set_type_reg(Type, Reg, Vst0); - _ -> - error(bad_type_info) - end; + OldType = get_durable_term_type(Reg, Vst0), + NewType = case meet(NewType0, OldType) of + none -> error({bad_type_info, Reg, NewType0, OldType}); + T -> T + end, + Type = propagate_fragility(NewType, [Reg], Vst0), + set_aliased_type(Type, Reg, Vst0); valfun_1({'%',_}, Vst) -> Vst; valfun_1({line,_}, Vst) -> @@ -643,7 +669,12 @@ valfun_4({gc_bif,Op,{f,Fail},Live,Src,Dst}, #vst{current=St0}=Vst0) -> Vst1 = Vst0#vst{current=St}, Vst2 = branch_state(Fail, Vst1), Vst3 = prune_x_regs(Live, Vst2), + SrcType = get_term_type(hd(Src), Vst3), Vst = case Op of + length when SrcType =/= cons, SrcType =/= nil -> + %% If we already know we have a cons cell or nil, it + %% shouldn't be demoted to list. + set_type(list, hd(Src), Vst3); map_size -> set_type(map, hd(Src), Vst3); _ -> @@ -786,6 +817,12 @@ valfun_4({bs_set_position, Ctx, Pos}, Vst) -> Vst; %% Other test instructions. +valfun_4({test,is_atom,{f,Lbl},[Src]}, Vst) -> + assert_term(Src, Vst), + set_aliased_type({atom,[]}, Src, branch_state(Lbl, Vst)); +valfun_4({test,is_boolean,{f,Lbl},[Src]}, Vst) -> + assert_term(Src, Vst), + set_aliased_type(bool, Src, branch_state(Lbl, Vst)); valfun_4({test,is_float,{f,Lbl},[Float]}, Vst) -> assert_term(Float, Vst), set_type({float,[]}, Float, branch_state(Lbl, Vst)); @@ -793,6 +830,9 @@ valfun_4({test,is_tuple,{f,Lbl},[Tuple]}, Vst) -> Type0 = get_term_type(Tuple, Vst), Type = upgrade_tuple_type({tuple,[0]}, Type0), set_aliased_type(Type, Tuple, branch_state(Lbl, Vst)); +valfun_4({test,is_integer,{f,Lbl},[Src]}, Vst) -> + assert_term(Src, Vst), + set_aliased_type({integer,[]}, Src, branch_state(Lbl, Vst)); valfun_4({test,is_nonempty_list,{f,Lbl},[Cons]}, Vst) -> assert_term(Cons, Vst), Type = cons, @@ -809,6 +849,14 @@ valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) -> assert_type(map, Src, Vst), assert_unique_map_keys(List), branch_state(Lbl, Vst); +valfun_4({test,is_list,{f,Lbl},[Src]}, Vst) -> + validate_src([Src], Vst), + Type = case get_term_type(Src, Vst) of + cons -> cons; + nil -> nil; + _ -> list + end, + set_aliased_type(Type, Src, branch_state(Lbl, Vst)); valfun_4({test,is_map,{f,Lbl},[Src]}, Vst0) -> Vst = branch_state(Lbl, Vst0), case Src of @@ -820,20 +868,28 @@ valfun_4({test,is_map,{f,Lbl},[Src]}, Vst0) -> _ -> kill_state(Vst0) end; +valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst0) -> + Vst = case get_term_type(Src, Vst0) of + list -> + branch_state(Lbl, set_aliased_type(cons, Src, Vst0)); + _ -> + branch_state(Lbl, Vst0) + end, + set_aliased_type(nil, Src, Vst); valfun_4({test,is_eq_exact,{f,Lbl},[Src,Val]=Ss}, Vst0) -> validate_src(Ss, Vst0), Infer = infer_types(Src, Vst0), Vst1 = Infer(Val, Vst0), - Vst = branch_state(Lbl, Vst1), - case Val of - {literal,Tuple} when is_tuple(Tuple) -> - Type0 = get_term_type(Val, Vst), - Type = upgrade_tuple_type({tuple,tuple_size(Tuple)}, - Type0), - set_aliased_type(Type, Src, Vst); - _ -> - Vst - end; + Vst2 = upgrade_ne_types(Src, Val, Vst1), + Vst3 = branch_state(Lbl, Vst2), + Vst = Vst3#vst{current=Vst1#vst.current}, + upgrade_eq_types(Src, Val, Vst); +valfun_4({test,is_ne_exact,{f,Lbl},[Src,Val]=Ss}, Vst0) -> + validate_src(Ss, Vst0), + Vst1 = upgrade_eq_types(Src, Val, Vst0), + Vst2 = branch_state(Lbl, Vst1), + Vst = Vst2#vst{current=Vst0#vst.current}, + upgrade_ne_types(Src, Val, Vst); valfun_4({test,_Op,{f,Lbl},Src}, Vst) -> validate_src(Src, Vst), branch_state(Lbl, Vst); @@ -920,6 +976,25 @@ valfun_4({get_map_elements,{f,Fail},Src,{list,List}}, Vst) -> valfun_4(_, _) -> error(unknown_instruction). +upgrade_ne_types(Src1, Src2, Vst0) -> + T1 = get_durable_term_type(Src1, Vst0), + T2 = get_durable_term_type(Src2, Vst0), + Type = subtract(T1, T2), + set_aliased_type(Type, Src1, Vst0). + +upgrade_eq_types(Src1, Src2, Vst0) -> + T1 = get_durable_term_type(Src1, Vst0), + T2 = get_durable_term_type(Src2, Vst0), + Meet = meet(T1, T2), + Vst = case T1 =/= Meet of + true -> set_aliased_type(Meet, Src1, Vst0); + false -> Vst0 + end, + case T2 =/= Meet of + true -> set_aliased_type(Meet, Src2, Vst); + false -> Vst + end. + verify_get_map(Fail, Src, List, Vst0) -> assert_not_literal(Src), %OTP 22. assert_type(map, Src, Vst0), @@ -1046,39 +1121,58 @@ verify_call_args_1(N, Vst) -> verify_call_args_1(X, Vst). verify_local_call(Lbl, Live, Vst) -> - F = fun({R, _Ctx}) -> - verify_call_match_context(Lbl, R, Vst) - end, - MsRegs = all_ms_in_x_regs(Live, Vst), - verify_no_ms_aliases(MsRegs), - foreach(F, MsRegs). + F = fun({R, Type}) -> + verify_arg_type(Lbl, R, Type, Vst) + end, + TRegs = typed_call_regs(Live, Vst), + verify_no_ms_aliases(TRegs), + foreach(F, TRegs). -all_ms_in_x_regs(0, _Vst) -> +typed_call_regs(0, _Vst) -> []; -all_ms_in_x_regs(Live0, Vst) -> +typed_call_regs(Live0, Vst) -> Live = Live0 - 1, R = {x,Live}, - case get_move_term_type(R, Vst) of - #ms{}=M -> [{R,M} | all_ms_in_x_regs(Live, Vst)]; - _ -> all_ms_in_x_regs(Live, Vst) - end. + [{R, get_move_term_type(R, Vst)} | typed_call_regs(Live, Vst)]. %% Verifies that the same match context isn't present twice. -verify_no_ms_aliases(MsRegs) -> - CtxIds = [Id || {_, #ms{id=Id}} <- MsRegs], +verify_no_ms_aliases(Regs) -> + CtxIds = [Id || {_, #ms{id=Id}} <- Regs], UniqueCtxIds = ordsets:from_list(CtxIds), if length(UniqueCtxIds) < length(CtxIds) -> - error({multiple_match_contexts, MsRegs}); + error({multiple_match_contexts, Regs}); length(UniqueCtxIds) =:= length(CtxIds) -> ok end. -%% Verifies that the target label accepts match contexts in the given register. -verify_call_match_context(Lbl, Ctx, #vst{ft=Ft}) -> - case gb_trees:lookup({Lbl, Ctx}, Ft) of - {value, match_context} -> ok; - none -> error(no_bs_start_match2) +%% Verifies that the given argument narrows to what the function expects. +verify_arg_type(Lbl, Reg, #ms{}, #vst{ft=Ft}) -> + %% Match contexts require explicit support, and may not be passed to a + %% function that accepts arbitrary terms. + case gb_trees:lookup({Lbl, Reg}, Ft) of + {value, #ms{}} -> ok; + _ -> error(no_bs_start_match2) + end; +verify_arg_type(Lbl, Reg, GivenType, #vst{ft=Ft}) -> + case gb_trees:lookup({Lbl, Reg}, Ft) of + {value, bool} when GivenType =:= {atom, true}; + GivenType =:= {atom, false}; + GivenType =:= {atom, []} -> + %% We don't yet support upgrading true/false to bool, so we + %% assume unknown atoms can be bools when validating calls. + ok; + {value, #ms{}} -> + %% Functions that accept match contexts also accept all other + %% terms. This will change once we support union types. + ok; + {value, RequiredType} -> + case meet(GivenType, RequiredType) of + none -> error({bad_arg_type, Reg, GivenType, RequiredType}); + _ -> ok + end; + none -> + ok end. allocate(Zero, Stk, Heap, Live, #vst{current=#st{numy=none}}=Vst0) -> @@ -1298,27 +1392,27 @@ bsm_restore(Reg, SavePoint, Vst) -> _ -> error({illegal_restore,SavePoint,range}) end. - select_val_branches(Src, Choices, Vst) -> Infer = infer_types(Src, Vst), - select_val_branches_1(Choices, Infer, Vst). + select_val_branches_1(Choices, Src, Infer, Vst). -select_val_branches_1([Val,{f,L}|T], Infer, Vst0) -> - Vst = branch_state(L, Infer(Val, Vst0)), - select_val_branches_1(T, Infer, Vst); -select_val_branches_1([], _, Vst) -> Vst. +select_val_branches_1([Val,{f,L}|T], Src, Infer, Vst0) -> + Vst1 = set_aliased_type(Val, Src, Infer(Val, Vst0)), + Vst = branch_state(L, Vst1), + select_val_branches_1(T, Src, Infer, Vst); +select_val_branches_1([], _, _, Vst) -> Vst. infer_types(Src, Vst) -> case get_def(Src, Vst) of {bif,is_map,{f,_},[Map],_} -> - fun({atom,true}, S) -> set_type_reg(map, Map, S); + fun({atom,true}, S) -> set_aliased_type(map, Map, S); (_, S) -> S end; {bif,tuple_size,{f,_},[Tuple],_} -> fun({integer,Arity}, S) -> Type0 = get_term_type(Tuple, S), Type = upgrade_tuple_type({tuple,Arity}, Type0), - set_type(Type, Tuple, S); + set_aliased_type(Type, Tuple, S); (_, S) -> S end; {bif,'=:=',{f,_},[ArityReg,{integer,_}=Val],_} when ArityReg =/= Src -> @@ -1366,8 +1460,10 @@ kill_aliases(Reg, #st{aliases=Aliases0}=St) -> St end. -set_type(Type, {x,_}=Reg, Vst) -> set_type_reg(Type, Reg, Vst); -set_type(Type, {y,_}=Reg, Vst) -> set_type_y(Type, Reg, Vst); +set_type(Type, {x,_}=Reg, Vst) -> + set_type_reg(Type, Reg, Reg, Vst); +set_type(Type, {y,_}=Reg, Vst) -> + set_type_reg(Type, Reg, Reg, Vst); set_type(_, _, #vst{}=Vst) -> Vst. set_type_reg(Type, Src, Dst, Vst) -> @@ -1507,12 +1603,16 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}). %% %% term Any valid Erlang (but not of the special types above). %% +%% binary Binary or bitstring. +%% %% bool The atom 'true' or the atom 'false'. %% %% cons Cons cell: [_|_] %% %% nil Empty list: [] %% +%% list List: [] or [_|_] +%% %% {tuple,[Sz]} Tuple. An element has been accessed using %% element/2 or setelement/3 so that it is known that %% the type is a tuple of size at least Sz. @@ -1533,7 +1633,7 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}). %% %% map Map. %% -%% +%% none A conflict in types. There will be an exception at runtime. %% %% FRAGILITY %% --------- @@ -1546,6 +1646,51 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}). %% Such terms are wrapped in a {fragile,Type} tuple, where Type is one %% of the types described above. +%% meet(Type1, Type2) -> Type +%% Return the meet of two types. The meet is a more specific type. +%% It will be 'none' if the types are in conflict. + +meet(Same, Same) -> + Same; +meet(term, Other) -> + Other; +meet(Other, term) -> + Other; +meet(T1, T2) -> + case {erlang:min(T1, T2),erlang:max(T1, T2)} of + {{atom,_}=A,{atom,[]}} -> A; + {bool,{atom,B}=Atom} when is_boolean(B) -> Atom; + {bool,{atom,[]}} -> bool; + {cons,list} -> cons; + {{float,_}=T,{float,[]}} -> T; + {{integer,_}=T,{integer,[]}} -> T; + {list,nil} -> nil; + {number,{integer,_}=T} -> T; + {number,{float,_}=T} -> T; + {{tuple,Size1},{tuple,Size2}} -> + case {Size1,Size2} of + {[Sz1],[Sz2]} -> + {tuple,[erlang:max(Sz1, Sz2)]}; + {Sz1,[Sz2]} when Sz2 =< Sz1 -> + {tuple,Sz1}; + {_,_} -> + none + end; + {_,_} -> none + end. + +%% subtract(Type1, Type2) -> Type +%% Subtract Type2 from Type2. Example: +%% subtract(list, nil) -> cons + +subtract(list, nil) -> cons; +subtract(list, cons) -> nil; +subtract(number, {integer,[]}) -> {float,[]}; +subtract(number, {float,[]}) -> {integer,[]}; +subtract(bool, {atom,false}) -> {atom, true}; +subtract(bool, {atom,true}) -> {atom, false}; +subtract(Type, _) -> Type. + assert_type(WantedType, Term, Vst) -> case get_term_type(Term, Vst) of {fragile,Type} -> @@ -1579,25 +1724,27 @@ assert_type(Needed, Actual) -> %% be executed at run-time. upgrade_tuple_type(NewType, {fragile,OldType}) -> - make_fragile(upgrade_tuple_type_1(NewType, OldType)); + Type = upgrade_tuple_type_1(NewType, OldType), + make_fragile(Type); upgrade_tuple_type(NewType, OldType) -> upgrade_tuple_type_1(NewType, OldType). -upgrade_tuple_type_1({tuple,[Sz]}, {tuple,[OldSz]}=T) when Sz < OldSz -> - %% The old type has a higher value for the least tuple size. - T; -upgrade_tuple_type_1({tuple,[Sz]}, {tuple,OldSz}=T) - when is_integer(Sz), is_integer(OldSz), Sz =< OldSz -> - %% The old size is exact, and the new size is smaller than the old size. - T; -upgrade_tuple_type_1({tuple,_}=T, _) -> - %% The new type information is exact or has a higher value for - %% the least tuple size. - %% Note that inconsistencies are also handled in this - %% clause, e.g. if the old type was an integer or a tuple accessed - %% outside its size; inconsistences will generally cause an exception - %% at run-time but are safe from our point of view. - T. +upgrade_tuple_type_1(NewType, OldType) -> + case meet(NewType, OldType) of + none -> + %% Unoptimized code may look like this: + %% + %% {test,is_list,Fail,[Reg]}. + %% {test,is_tuple,Fail,[Reg]}. + %% {test,test_arity,Fail,[Reg,5]}. + %% + %% Note that the test_arity instruction can never be reached. + %% To make sure it's not rejected, set the type of Reg to + %% NewType instead of 'none'. + NewType; + Type -> + Type + end. get_tuple_size({integer,[]}) -> 0; get_tuple_size({integer,Sz}) -> Sz; @@ -1606,6 +1753,17 @@ get_tuple_size(_) -> 0. validate_src(Ss, Vst) when is_list(Ss) -> foreach(fun(S) -> get_term_type(S, Vst) end, Ss). +%% get_durable_term_type(Src, ValidatorState) -> Type +%% Get the type of the source Src. The returned type Type will be +%% a standard Erlang type (no catch/try tags or match contexts). +%% Fragility will be stripped. + +get_durable_term_type(Src, Vst) -> + case get_term_type(Src, Vst) of + {fragile,Type} -> Type; + Type -> Type + end. + %% get_move_term_type(Src, ValidatorState) -> Type %% Get the type of the source Src. The returned type Type will be %% a standard Erlang type (no catch/try tags). Match contexts are OK. @@ -1639,6 +1797,8 @@ get_term_type_1(nil=T, _) -> T; get_term_type_1({atom,A}=T, _) when is_atom(A) -> T; get_term_type_1({float,F}=T, _) when is_float(F) -> T; get_term_type_1({integer,I}=T, _) when is_integer(I) -> T; +get_term_type_1({literal,[_|_]}, _) -> cons; +get_term_type_1({literal,Bitstring}, _) when is_bitstring(Bitstring) -> binary; get_term_type_1({literal,Map}, _) when is_map(Map) -> map; get_term_type_1({literal,Tuple}, _) when is_tuple(Tuple) -> {tuple,tuple_size(Tuple)}; @@ -1744,7 +1904,7 @@ merge_regs_1([{R1,_}|Rs1], [{R2,_}|_]=Rs2) when R1 < R2 -> merge_regs_1([{R1,_}|_]=Rs1, [{R2,_}|Rs2]) when R1 > R2 -> merge_regs_1(Rs1, Rs2); merge_regs_1([{R,Type1}|Rs1], [{R,Type2}|Rs2]) -> - [{R,merge_types(Type1, Type2)}|merge_regs_1(Rs1, Rs2)]; + [{R,join(Type1, Type2)}|merge_regs_1(Rs1, Rs2)]; merge_regs_1([], []) -> []; merge_regs_1([], [_|_]) -> []; merge_regs_1([_|_], []) -> []. @@ -1763,63 +1923,90 @@ merge_y_regs_1(Y, S, Regs0) when Y >= 0 -> Type0 -> merge_y_regs_1(Y-1, S, Regs0); Type1 -> - Type = merge_types(Type0, Type1), + Type = join(Type0, Type1), Regs = gb_trees:update(Y, Type, Regs0), merge_y_regs_1(Y-1, S, Regs) end; merge_y_regs_1(_, _, Regs) -> Regs. -%% merge_types(Type1, Type2) -> Type +%% join(Type1, Type2) -> Type %% Return the most specific type possible. %% Note: Type1 must NOT be the same as Type2. -merge_types({fragile,Same}=Type, Same) -> +join({literal,_}=T1, T2) -> + join_literal(T1, T2); +join(T1, {literal,_}=T2) -> + join_literal(T2, T1); +join({fragile,Same}=Type, Same) -> Type; -merge_types({fragile,T1}, T2) -> - make_fragile(merge_types(T1, T2)); -merge_types(Same, {fragile,Same}=Type) -> +join({fragile,T1}, T2) -> + make_fragile(join(T1, T2)); +join(Same, {fragile,Same}=Type) -> Type; -merge_types(T1, {fragile,T2}) -> - make_fragile(merge_types(T1, T2)); -merge_types(uninitialized=I, _) -> I; -merge_types(_, uninitialized=I) -> I; -merge_types(initialized=I, _) -> I; -merge_types(_, initialized=I) -> I; -merge_types({catchtag,T0},{catchtag,T1}) -> +join(T1, {fragile,T2}) -> + make_fragile(join(T1, T2)); +join(uninitialized=I, _) -> I; +join(_, uninitialized=I) -> I; +join(initialized=I, _) -> I; +join(_, initialized=I) -> I; +join({catchtag,T0},{catchtag,T1}) -> {catchtag,ordsets:from_list(T0++T1)}; -merge_types({trytag,T0},{trytag,T1}) -> +join({trytag,T0},{trytag,T1}) -> {trytag,ordsets:from_list(T0++T1)}; -merge_types({tuple,A}, {tuple,B}) -> +join({tuple,A}, {tuple,B}) -> {tuple,[min(tuple_sz(A), tuple_sz(B))]}; -merge_types({Type,A}, {Type,B}) +join({Type,A}, {Type,B}) when Type =:= atom; Type =:= integer; Type =:= float -> if A =:= B -> {Type,A}; true -> {Type,[]} end; -merge_types({Type,_}, number) +join({Type,_}, number) when Type =:= integer; Type =:= float -> number; -merge_types(number, {Type,_}) +join(number, {Type,_}) when Type =:= integer; Type =:= float -> number; -merge_types(bool, {atom,A}) -> +join(bool, {atom,A}) -> merge_bool(A); -merge_types({atom,A}, bool) -> +join({atom,A}, bool) -> merge_bool(A); -merge_types(cons, {literal,[_|_]}) -> - cons; -merge_types({literal,[_|_]}, cons) -> - cons; -merge_types({literal,[_|_]}, {literal,[_|_]}) -> - cons; -merge_types(#ms{id=Id1,valid=B1,slots=Slots1}, +join({atom,_}, {atom,_}) -> + {atom,[]}; +join(#ms{id=Id1,valid=B1,slots=Slots1}, #ms{id=Id2,valid=B2,slots=Slots2}) -> Id = if Id1 =:= Id2 -> Id1; true -> make_ref() end, #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)}; -merge_types(T1, T2) when T1 =/= T2 -> - %% Too different. All we know is that the type is a 'term'. +join(T1, T2) when T1 =/= T2 -> + %% We've exhaused all other options, so the type must either be a list or + %% a 'term'. + join_list(T1, T2). + +%% Merges types of literals. Note that the left argument must either be a +%% literal or exactly equal to the second argument. +join_literal(Same, Same) -> + Same; +join_literal({literal,[_|_]}, T) -> + join_literal(T, cons); +join_literal({literal,#{}}, T) -> + join_literal(T, map); +join_literal({literal,Tuple}, T) when is_tuple(Tuple) -> + join_literal(T, {tuple, tuple_size(Tuple)}); +join_literal({literal,_}, T) -> + %% Bitstring, fun, or similar. + join_literal(T, term); +join_literal(T1, T2) -> + %% We're done extracting the types, try merging them again. + join(T1, T2). + +join_list(nil, cons) -> list; +join_list(nil, list) -> list; +join_list(cons, list) -> list; +join_list(T, nil) -> join_list(nil, T); +join_list(T, cons) -> join_list(cons, T); +join_list(_, _) -> + %% Not a list, so it must be a term. term. tuple_sz([Sz]) -> Sz; @@ -1930,6 +2117,9 @@ bif_type(abs, [Num], Vst) -> end; bif_type(float, _, _) -> {float,[]}; bif_type('/', _, _) -> {float,[]}; +%% Binary operations +bif_type('byte_size', _, _) -> {integer,[]}; +bif_type('bit_size', _, _) -> {integer,[]}; %% Integer operations. bif_type(ceil, [_], _) -> {integer,[]}; bif_type('div', [_,_], _) -> {integer,[]}; @@ -2009,11 +2199,13 @@ is_bif_safe(_, _) -> false. arith_type([A], Vst) -> %% Unary '+' or '-'. case get_term_type(A, Vst) of + {integer,_} -> {integer,[]}; {float,_} -> {float,[]}; _ -> number end; arith_type([A,B], Vst) -> case {get_term_type(A, Vst),get_term_type(B, Vst)} of + {{integer,_},{integer,_}} -> {integer,[]}; {{float,_},_} -> {float,[]}; {_,{float,_}} -> {float,[]}; {_,_} -> number @@ -2039,6 +2231,14 @@ return_type_1(erlang, setelement, 3, Vst) -> {integer,I} -> upgrade_tuple_type({tuple,[I]}, TupleType); _ -> TupleType end; +return_type_1(erlang, '++', 2, Vst) -> + case get_term_type({x,0}, Vst) =:= cons orelse + get_term_type({x,1}, Vst) =:= cons of + true -> cons; + false -> list + end; +return_type_1(erlang, '--', 2, _Vst) -> + list; return_type_1(erlang, F, A, _) -> return_type_erl(F, A); return_type_1(math, F, A, _) -> diff --git a/lib/compiler/src/beam_z.erl b/lib/compiler/src/beam_z.erl index 677094b3cd..415b579240 100644 --- a/lib/compiler/src/beam_z.erl +++ b/lib/compiler/src/beam_z.erl @@ -71,6 +71,31 @@ undo_renames([{get_hd,Src,Dst1},{get_tl,Src,Dst2}|Is]) -> [{get_list,Src,Dst1,Dst2}|undo_renames(Is)]; undo_renames([{get_tl,Src,Dst2},{get_hd,Src,Dst1}|Is]) -> [{get_list,Src,Dst1,Dst2}|undo_renames(Is)]; +undo_renames([{bs_put,_,{bs_put_binary,1,_}, + [{atom,all},{literal,<<>>}]}|Is]) -> + undo_renames(Is); +undo_renames([{bs_put,Fail,{bs_put_binary,1,_Flags}, + [{atom,all},{literal,BinString}]}|Is0]) -> + Bits = bit_size(BinString), + Bytes = Bits div 8, + case Bits rem 8 of + 0 -> + I = {bs_put_string,byte_size(BinString), + {string,BinString}}, + [undo_rename(I)|undo_renames(Is0)]; + Rem -> + <<Binary:Bytes/bytes,Int:Rem>> = BinString, + PutInt = {bs_put_integer,Fail,{integer,Rem},1, + {field_flags,[unsigned,big]},{integer,Int}}, + Is = [PutInt|undo_renames(Is0)], + case Binary of + <<>> -> + Is; + _ -> + [{bs_put_string,byte_size(Binary), + {string,Binary}}|Is] + end + end; undo_renames([I|Is]) -> [undo_rename(I)|undo_renames(Is)]; undo_renames([]) -> []. @@ -79,8 +104,6 @@ undo_rename({bs_put,F,{I,U,Fl},[Sz,Src]}) -> {I,F,Sz,U,Fl,Src}; undo_rename({bs_put,F,{I,Fl},[Src]}) -> {I,F,Fl,Src}; -undo_rename({bs_put,{f,0},{bs_put_string,_,_}=I,[]}) -> - I; undo_rename({bif,bs_add=I,F,[Src1,Src2,{integer,U}],Dst}) -> {I,F,[Src1,Src2,U],Dst}; undo_rename({bif,bs_utf8_size=I,F,[Src],Dst}) -> @@ -101,7 +124,7 @@ undo_rename({test,bs_match_string=Op,F,[Ctx,Bin0]}) -> 0 -> Bin0; Rem -> <<Bin0/bitstring,0:(8-Rem)>> end, - {test,Op,F,[Ctx,Bits,{string,binary_to_list(Bin)}]}; + {test,Op,F,[Ctx,Bits,{string,Bin}]}; undo_rename({put_map,Fail,assoc,S,D,R,L}) -> {put_map_assoc,Fail,S,D,R,L}; undo_rename({put_map,Fail,exact,S,D,R,L}) -> diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 65c4f140c9..53d3cec2d7 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -268,6 +268,10 @@ expand_opt(r21, Os) -> [no_put_tuple2 | expand_opt(no_bsm3, Os)]; expand_opt({debug_info_key,_}=O, Os) -> [encrypt_debug_info,O|Os]; +expand_opt(no_type_opt, Os) -> + [no_ssa_opt_type_start, + no_ssa_opt_type_continue, + no_ssa_opt_type_finish | Os]; expand_opt(O, Os) -> [O|Os]. expand_opt_before_21(Os) -> @@ -823,6 +827,9 @@ kernel_passes() -> {pass,beam_kernel_to_ssa}, {iff,dssa,{listing,"ssa"}}, {iff,ssalint,{pass,beam_ssa_lint}}, + {unless,no_share_opt,{pass,beam_ssa_share}}, + {iff,dssashare,{listing,"ssashare"}}, + {iff,ssalint,{pass,beam_ssa_lint}}, {unless,no_bsm_opt,{pass,beam_ssa_bsm}}, {iff,dssabsm,{listing,"ssabsm"}}, {iff,ssalint,{pass,beam_ssa_lint}}, @@ -852,8 +859,6 @@ asm_passes() -> {iff,dblk,{listing,"block"}}, {unless,no_except,{pass,beam_except}}, {iff,dexcept,{listing,"except"}}, - {unless,no_bs_opt,{pass,beam_bs}}, - {iff,dbs,{listing,"bs"}}, {unless,no_jopt,{pass,beam_jump}}, {iff,djmp,{listing,"jump"}}, {unless,no_peep_opt,{pass,beam_peep}}, @@ -2081,7 +2086,6 @@ pre_load() -> L = [beam_a, beam_asm, beam_block, - beam_bs, beam_clean, beam_dict, beam_except, @@ -2098,6 +2102,7 @@ pre_load() -> beam_ssa_opt, beam_ssa_pre_codegen, beam_ssa_recv, + beam_ssa_share, beam_ssa_type, beam_trim, beam_utils, diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src index 86259270bd..108a0ca100 100644 --- a/lib/compiler/src/compiler.app.src +++ b/lib/compiler/src/compiler.app.src @@ -24,7 +24,6 @@ beam_a, beam_asm, beam_block, - beam_bs, beam_clean, beam_dict, beam_disasm, @@ -45,6 +44,7 @@ beam_ssa_pp, beam_ssa_pre_codegen, beam_ssa_recv, + beam_ssa_share, beam_ssa_type, beam_trim, beam_utils, diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl index ce9762899e..d925decce6 100644 --- a/lib/compiler/src/erl_bifs.erl +++ b/lib/compiler/src/erl_bifs.erl @@ -195,6 +195,7 @@ is_safe(erlang, is_float, 1) -> true; is_safe(erlang, is_function, 1) -> true; is_safe(erlang, is_integer, 1) -> true; is_safe(erlang, is_list, 1) -> true; +is_safe(erlang, is_map, 1) -> true; is_safe(erlang, is_number, 1) -> true; is_safe(erlang, is_pid, 1) -> true; is_safe(erlang, is_port, 1) -> true; diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index d848cd8f19..43c99be982 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -2667,12 +2667,20 @@ opt_build_stacktrace(#c_let{vars=[#c_var{name=Cooked}], #c_call{module=#c_literal{val=erlang}, name=#c_literal{val=raise}, args=[Class,Exp,#c_var{name=Cooked}]} -> - %% The stacktrace is only used in a call to erlang:raise/3. - %% There is no need to build the stacktrace. Replace the - %% call to erlang:raise/3 with the the raw_raise/3 instruction, - %% which will use a raw stacktrace. - #c_primop{name=#c_literal{val=raw_raise}, - args=[Class,Exp,RawStk]}; + case core_lib:is_var_used(Cooked, #c_cons{hd=Class,tl=Exp}) of + true -> + %% Not safe. The stacktrace is used in the class or + %% reason. + Let; + false -> + %% The stacktrace is only used in the last + %% argument for erlang:raise/3. There is no need + %% to build the stacktrace. Replace the call to + %% erlang:raise/3 with the the raw_raise/3 + %% instruction, which will use a raw stacktrace. + #c_primop{name=#c_literal{val=raw_raise}, + args=[Class,Exp,RawStk]} + end; #c_let{vars=[#c_var{name=V}],arg=Arg,body=B0} when V =/= Cooked -> case core_lib:is_var_used(Cooked, Arg) of false -> diff --git a/lib/compiler/src/sys_core_inline.erl b/lib/compiler/src/sys_core_inline.erl index 5a6cc45e4a..3380e3f1bd 100644 --- a/lib/compiler/src/sys_core_inline.erl +++ b/lib/compiler/src/sys_core_inline.erl @@ -195,6 +195,9 @@ kill_id_anns(Body) -> cerl_trees:map(fun(#c_fun{anno=A0}=CFun) -> A = kill_id_anns_1(A0), CFun#c_fun{anno=A}; + (#c_var{anno=A0}=Var) -> + A = kill_id_anns_1(A0), + Var#c_var{anno=A}; (Expr) -> %% Mark everything as compiler generated to %% suppress bogus warnings. diff --git a/lib/compiler/test/Makefile b/lib/compiler/test/Makefile index 40428b7f2d..f042a5cb51 100644 --- a/lib/compiler/test/Makefile +++ b/lib/compiler/test/Makefile @@ -105,6 +105,8 @@ CORE_MODULES = \ lfe_andor_SUITE \ lfe_guard_SUITE +NO_MOD_OPT = $(NO_OPT) + NO_OPT_MODULES= $(NO_OPT:%=%_no_opt_SUITE) NO_OPT_ERL_FILES= $(NO_OPT_MODULES:%=%.erl) POST_OPT_MODULES= $(NO_OPT:%=%_post_opt_SUITE) @@ -113,6 +115,8 @@ INLINE_MODULES= $(INLINE:%=%_inline_SUITE) INLINE_ERL_FILES= $(INLINE_MODULES:%=%.erl) R21_MODULES= $(R21:%=%_r21_SUITE) R21_ERL_FILES= $(R21_MODULES:%=%.erl) +NO_MOD_OPT_MODULES= $(NO_MOD_OPT:%=%_no_module_opt_SUITE) +NO_MOD_OPT_ERL_FILES= $(NO_MOD_OPT_MODULES:%=%.erl) ERL_FILES= $(MODULES:%=%.erl) CORE_FILES= $(CORE_MODULES:%=%.core) @@ -142,7 +146,7 @@ EBIN = . # ---------------------------------------------------- make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \ - $(INLINE_ERL_FILES) $(R21_ERL_FILES) + $(INLINE_ERL_FILES) $(R21_ERL_FILES) $(NO_MOD_OPT_ERL_FILES) $(ERL_TOP)/make/make_emakefile $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) \ > $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +no_copt +no_postopt \ @@ -154,6 +158,8 @@ make_emakefile: $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \ -o$(EBIN) $(INLINE_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +r21 $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(R21_MODULES) >> $(EMAKEFILE) + $(ERL_TOP)/make/make_emakefile +no_module_opt $(ERL_COMPILE_FLAGS) \ + -o$(EBIN) $(NO_MOD_OPT_MODULES) >> $(EMAKEFILE) $(ERL_TOP)/make/make_emakefile +from_core $(ERL_COMPILE_FLAGS) \ -o$(EBIN) $(CORE_MODULES) >> $(EMAKEFILE) @@ -183,6 +189,9 @@ docs: %_r21_SUITE.erl: %_SUITE.erl sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ +%_no_module_opt_SUITE.erl: %_SUITE.erl + sed -e 's;-module($(basename $<));-module($(basename $@));' $< > $@ + # ---------------------------------------------------- # Release Target # ---------------------------------------------------- @@ -195,7 +204,8 @@ release_tests_spec: make_emakefile $(INSTALL_DATA) compiler.spec compiler.cover \ $(EMAKEFILE) $(ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(NO_OPT_ERL_FILES) $(POST_OPT_ERL_FILES) \ - $(INLINE_ERL_FILES) $(R21_ERL_FILES) "$(RELSYSDIR)" + $(INLINE_ERL_FILES) $(R21_ERL_FILES) \ + $(NO_MOD_OPT_ERL_FILES) "$(RELSYSDIR)" $(INSTALL_DATA) $(CORE_FILES) "$(RELSYSDIR)" for file in $(ERL_DUMMY_FILES); do \ module=`basename $$file .erl`; \ diff --git a/lib/compiler/test/apply_SUITE.erl b/lib/compiler/test/apply_SUITE.erl index 0f82a56fb7..2ee518b1a0 100644 --- a/lib/compiler/test/apply_SUITE.erl +++ b/lib/compiler/test/apply_SUITE.erl @@ -73,6 +73,7 @@ mfa(Config) when is_list(Config) -> {'EXIT',_} = (catch ?APPLY2(Mod, (id(bazzzzzz)), a, b)), {'EXIT',_} = (catch ?APPLY2({}, baz, a, b)), {'EXIT',_} = (catch ?APPLY2(?MODULE, [], a, b)), + {'EXIT',_} = (catch bad_literal_call(1)), ok = apply(Mod, foo, id([])), {[a,b|c]} = apply(Mod, bar, id([[a,b|c]])), @@ -92,6 +93,13 @@ mfa(Config) when is_list(Config) -> apply(Mod, foo, []). +%% The single call to this function with a literal argument caused type +%% optimization to swap out the 'mod' field of a #b_remote{}, which was +%% mishandled during code generation as it assumed that the module would always +%% be an atom. +bad_literal_call(I) -> + I:foo(). + foo() -> ok. diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl index 40eb6f06c3..759d884dc4 100644 --- a/lib/compiler/test/beam_jump_SUITE.erl +++ b/lib/compiler/test/beam_jump_SUITE.erl @@ -22,7 +22,8 @@ -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,ambiguous_catch_try_state/1, - unsafe_move_elimination/1,build_tuple/1]). + unsafe_move_elimination/1,build_tuple/1, + coverage/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -35,7 +36,8 @@ groups() -> [undefined_label, ambiguous_catch_try_state, unsafe_move_elimination, - build_tuple + build_tuple, + coverage ]}]. init_per_suite(Config) -> @@ -126,6 +128,44 @@ do_build_tuple(Message) -> {Message#message3.id, Res} end. +coverage(_Config) -> + ok = coverage_1(ok), + {error,badarg} = coverage_1({error,badarg}), + + gt = coverage_2(100, 42), + le = coverage_2(100, 999), + le = coverage_2([], []), + gt = coverage_2([], xxx), + + ok. + +coverage_1(Var) -> + case id(Var) of + ok -> ok; + Error -> Error + end. + +%% Cover beam_jump:invert_test(is_ne_exact). +coverage_2(Pre1, Pre2) -> + case + case Pre1 == [] of + false -> + false; + true -> + Pre2 /= [] + end + of + true -> + gt; + false -> + case Pre1 > Pre2 of + true -> + gt; + false -> + le + end + end. + id(I) -> I. diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl index 5536abbdde..15cf9bcbf3 100644 --- a/lib/compiler/test/beam_ssa_SUITE.erl +++ b/lib/compiler/test/beam_ssa_SUITE.erl @@ -22,7 +22,7 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, calls/1,tuple_matching/1,recv/1,maps/1, - cover_ssa_dead/1,combine_sw/1]). + cover_ssa_dead/1,combine_sw/1,share_opt/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -36,7 +36,8 @@ groups() -> recv, maps, cover_ssa_dead, - combine_sw + combine_sw, + share_opt ]}]. init_per_suite(Config) -> @@ -91,7 +92,13 @@ start_it([_|_]=MFA) -> end. tuple_matching(_Config) -> - do_tuple_matching({tag,42}). + do_tuple_matching({tag,42}), + + true = is_two_tuple({a,b}), + false = is_two_tuple({a,b,c}), + false = is_two_tuple(atom), + + ok. do_tuple_matching(Arg) -> Res = do_tuple_matching_1(Arg), @@ -117,6 +124,12 @@ do_tuple_matching_3(Tuple) when is_tuple(Tuple) -> {ok,element(2, Tuple)} end. +is_two_tuple(Arg) -> + case is_tuple(Arg) of + false -> false; + true -> tuple_size(Arg) == 2 + end. + -record(reporter_state, {res,run_config}). -record(run_config, {report_interval=0}). @@ -467,5 +480,18 @@ do_comb_sw_2(X) -> end, erase(?MODULE). +share_opt(_Config) -> + ok = do_share_opt(0). + +do_share_opt(A) -> + %% The compiler would be stuck in an infinite loop in beam_ssa_share. + case A of + 0 -> a; + 1 -> b; + 2 -> c + end, + receive after 1 -> ok end. + + %% The identity function. id(I) -> I. diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl index a4459b95bf..6efa98de44 100644 --- a/lib/compiler/test/beam_type_SUITE.erl +++ b/lib/compiler/test/beam_type_SUITE.erl @@ -116,8 +116,8 @@ do_integers_4(_, _, Res) -> Res. do_integers_5(X0, Y0) -> - %% X and Y will use the same register. - X = X0 band 1, + %% _X and Y will use the same register. + _X = X0 band 1, Y = Y0 band 3, case Y of 0 -> zero; @@ -213,6 +213,10 @@ coverage(Config) -> [_|_] -> ok end, + + %% Cover beam_type:verified_type(none). + {'EXIT',{badarith,_}} = (catch (id(2) / id(1)) band 16#ff), + ok. booleans(_Config) -> diff --git a/lib/compiler/test/beam_utils_SUITE.erl b/lib/compiler/test/beam_utils_SUITE.erl index ff0f72d519..eb0af59f9d 100644 --- a/lib/compiler/test/beam_utils_SUITE.erl +++ b/lib/compiler/test/beam_utils_SUITE.erl @@ -197,7 +197,7 @@ do_bs_init_4(Arg1, Arg2) -> id(Rewrite) end/binary, "/shared">>); - Other -> + _Other -> error end. @@ -553,7 +553,7 @@ not_used_p(_C, S, K, L) when is_record(K, k) -> id(K) end. -is_used_fr(Config) -> +is_used_fr(_Config) -> 1 = is_used_fr(self(), self()), 1 = is_used_fr(self(), other), receive 1 -> ok end, @@ -572,7 +572,7 @@ is_used_fr(X, Y) -> X ! 1. %% ERL-778. -unsafe_is_function(Config) -> +unsafe_is_function(_Config) -> {undefined,any} = unsafe_is_function(undefined, any), {ok,any} = unsafe_is_function(fun() -> ok end, any), {'EXIT',{{case_clause,_},_}} = (catch unsafe_is_function(fun(_) -> ok end, any)), diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 661b48a080..585d0e7191 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -579,14 +579,26 @@ receive_stacked(Config) -> ok. +aliased_types(Config) -> + Seq = lists:seq(1, 5), + 1 = aliased_types_1(Seq, Config), + + {1,1} = aliased_types_2(Seq), + {42,none} = aliased_types_2([]), + + gurka = aliased_types_3([gurka]), + gaffel = aliased_types_3([gaffel]), + + ok. + %% ERL-735: validator failed to track types on aliased registers, rejecting %% legitimate optimizations. %% %% move x0 y0 %% bif hd L1 x0 %% get_hd y0 %% The validator failed to see that y0 was a list -aliased_types(Config) when is_list(Config) -> - Bug = lists:seq(1, 5), +%% +aliased_types_1(Bug, Config) -> if Config =/= [gurka, gaffel] -> %% Pointless branch. _ = hd(Bug), @@ -594,6 +606,31 @@ aliased_types(Config) when is_list(Config) -> hd(Bug) end. +%% ERL-832: validator failed to realize that a Y register was a cons. +aliased_types_2(Bug) -> + Res = case Bug of + [] -> id(42); + _ -> hd(Bug) + end, + {Res,case Bug of + [] -> none; + _ -> hd(Bug) + end}. + +%% ERL-832 part deux; validator failed to realize that an aliased register was +%% a cons. +aliased_types_3(Bug) -> + List = [Y || Y <- Bug], + case List of + [] -> Bug; + _ -> + if + hd(List) -> a:a(); + true -> ok + end, + hd(List) + end. + %%%------------------------------------------------------------------------- transform_remove(Remove, Module) -> @@ -652,3 +689,6 @@ night(Turned) -> ok. participating(_, _, _, _) -> ok. + +id(I) -> + I. diff --git a/lib/compiler/test/bs_construct_SUITE.erl b/lib/compiler/test/bs_construct_SUITE.erl index ccc49df005..69017d87e7 100644 --- a/lib/compiler/test/bs_construct_SUITE.erl +++ b/lib/compiler/test/bs_construct_SUITE.erl @@ -153,6 +153,8 @@ l(I_13, I_big1, I_16, Bin) -> [0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, 16#77,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF,16#FF, 16#FF,16#FF,16#FF,16#FF,16#FF,16#FF]), + ?T(<< (<<"abc",7:3>>):3/binary >>, + [$a,$b,$c]), %% Mix different units. ?T(<<37558955:(I_16-12)/unit:8,1:1>>, @@ -311,6 +313,9 @@ fail(Config) when is_list(Config) -> {'EXIT',{badarg,_}} = (catch <<0:(-(1 bsl 100))>>), {'EXIT',{badarg,_}} = (catch <<Bin/binary,0:(-(1 bsl 100))>>), + %% Unaligned sizes with literal binaries. + {'EXIT',{badarg,_}} = (catch <<0,(<<7777:17>>)/binary>>), + ok. float_bin(Config) when is_list(Config) -> diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index 01f302ad21..2cfcb841a7 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -743,9 +743,19 @@ coverage(Config) when is_list(Config) -> bitstring = coverage_bitstring(<<7:4>>), other = coverage_bitstring([a]), + %% Cover code in beam_trim. + {done,<<17,53>>,[253,155,200]} = coverage_trim(<<253,155,200,17,53>>, e0, e1, e2, e3, []), + <<"(right|linux)">> = coverage_trim_1(<<"">>, <<"right">>, <<"linux">>), + <<"/(right|linux)">> = coverage_trim_1(<<"/">>, <<"right">>, <<"linux">>), + <<"(left|linux)/(right|linux)">> = + coverage_trim_1(<<"left">>, <<"right">>, <<"linux">>), + + {10,<<"-">>,""} = coverage_trim_2(<<"-">>, 10, []), + {8,<<"-">>,"aa"} = coverage_trim_2(<<"aa-">>, 10, []), + ok. coverage_fold(Fun, Acc, <<H,T/binary>>) -> @@ -848,6 +858,29 @@ coverage_trim(<<C:8,T/binary>> = Bin, E0, E1, E2, E3, Acc) -> {done,Bin,lists:reverse(Acc)} end. +coverage_trim_1(<<>>, Right, OsType) -> + do_coverage_trim_1(Right, OsType); +coverage_trim_1(<<"/">>, Right, OsType) -> + <<"/",(do_coverage_trim_1(Right, OsType))/binary>>; +coverage_trim_1(Left, Right, OsType) -> + <<(do_coverage_trim_1(Left, OsType))/binary, + "/", + (do_coverage_trim_1(Right, OsType))/binary>>. + +do_coverage_trim_1(A, OsType) -> + <<"(",A/binary,"|",OsType/binary,")">>. + +coverage_trim_2(<<C/utf8,R/binary>> = Bin, I, L) -> + case printable_char(C) of + true -> + coverage_trim_2(R, I - 1, [C | L]); + false -> + {I,Bin,lists:reverse(L)} + end. + +printable_char($a) -> true; +printable_char(_) -> false. + multiple_uses(Config) when is_list(Config) -> {344,62879,345,<<245,159,1,89>>} = multiple_uses_1(<<1,88,245,159,1,89>>), true = multiple_uses_2(<<0,0,197,18>>), @@ -1814,11 +1847,10 @@ do_erl_689_2b(_, <<Length, Data/binary>>) -> %% ERL-753 bs_start_match2_defs(_Config) -> - {<<"http://127.0.0.1:1234/vsaas/hello">>} = api_url(<<"hello">>, dummy), - {"https://127.0.0.1:4321/vsaas/hello"} = api_url({https, "hello"}, dummy). + {<<"http://127.0.0.1:1234/vsaas/hello">>} = api_url(<<"hello">>), + {"https://127.0.0.1:4321/vsaas/hello"} = api_url({https, "hello"}). -api_url(URL, Auth) -> - Header = [], +api_url(URL) -> case URL of <<_/binary>> -> {<<"http://127.0.0.1:1234/vsaas/",URL/binary>>}; {https, [_|_] = URL1} -> {"https://127.0.0.1:4321/vsaas/"++URL1} diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 8d8bbe9543..7452466666 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -391,7 +391,6 @@ do_file_listings(DataDir, PrivDir, [File|Files]) -> do_listing(Simple, TargetDir, dcg, ".codegen"), do_listing(Simple, TargetDir, dblk, ".block"), do_listing(Simple, TargetDir, dexcept, ".except"), - do_listing(Simple, TargetDir, dbs, ".bs"), do_listing(Simple, TargetDir, djmp, ".jump"), do_listing(Simple, TargetDir, dclean, ".clean"), do_listing(Simple, TargetDir, dpeep, ".peep"), @@ -1250,8 +1249,8 @@ do_opt_guards_fun([]) -> []. is_exception(guard_SUITE, {'-complex_not/1-fun-4-',1}) -> true; is_exception(guard_SUITE, {'-complex_not/1-fun-5-',1}) -> true; is_exception(guard_SUITE, {bad_guards,1}) -> true; -is_exception(guard_SUITE, {bad_guards_3,2}) -> true; -is_exception(guard_SUITE, {nested_not_2b,4}) -> true; +is_exception(guard_SUITE, {nested_not_2b,6}) -> true; %% w/o type optimization +is_exception(guard_SUITE, {nested_not_2b,2}) -> true; %% with type optimization is_exception(_, _) -> false. sys_pre_attributes(Config) -> diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 3fca1434ae..adfebd5158 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -502,7 +502,7 @@ source(true, Activities) -> Activities end. -tim(#{reduction := Emergency}) -> +tim(#{reduction := _Emergency}) -> try fun() -> surgery end catch diff --git a/lib/compiler/test/float_SUITE.erl b/lib/compiler/test/float_SUITE.erl index 012810aba2..831e8279aa 100644 --- a/lib/compiler/test/float_SUITE.erl +++ b/lib/compiler/test/float_SUITE.erl @@ -20,7 +20,8 @@ -module(float_SUITE). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, - pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1]). + pending/1,bif_calls/1,math_functions/1,mixed_float_and_int/1, + subtract_number_type/1]). -include_lib("common_test/include/ct.hrl"). @@ -28,7 +29,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [pending, bif_calls, math_functions, - mixed_float_and_int]. + mixed_float_and_int, subtract_number_type]. groups() -> []. @@ -176,5 +177,15 @@ mixed_float_and_int(Config) when is_list(Config) -> pc(Cov, NotCov, X) -> round(Cov/(Cov+NotCov)*100) + 42 + 2.0*X. +subtract_number_type(Config) when is_list(Config) -> + 120 = fact(5). + +fact(N) -> + fact(N, 1). + +fact(0, P) -> P; +fact(1, P) -> P; +fact(N, P) -> fact(N-1, P*N). + id(I) -> I. diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index 1c05129dc4..ed0a56f064 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -1295,6 +1295,32 @@ rel_ops(Config) when is_list(Config) -> Empty = id([]), ?T(==, [], Empty), + %% Cover beam_ssa_dead:turn_op('/='). + ok = (fun(A, B) when is_atom(A) -> + X = id(A /= B), + if + X -> ok; + true -> error + end + end)(a, b), + ok = (fun(A, B) when is_atom(A) -> + X = id(B /= A), + if + X -> ok; + true -> error + end + end)(a, b), + + %% Cover beam_ssa_dead. + Arrow = fun([T1,T2]) when T1 == $>, T2 == $>; + T1 == $<, T2 == $| -> true; + (_) -> false + end, + true = Arrow(">>"), + true = Arrow("<|"), + false = Arrow("><"), + false = Arrow(""), + ok. -undef(TestOp). @@ -1328,6 +1354,9 @@ rel_op_combinations_1(N, Digits) -> Bool = is_digit_6(N), Bool = is_digit_7(N), Bool = is_digit_8(N), + Bool = is_digit_9(42, N), + Bool = is_digit_10(N, 0), + Bool = is_digit_11(N, 0), rel_op_combinations_1(N-1, Digits). is_digit_1(X) when 16#0660 =< X, X =< 16#0669 -> true; @@ -1371,6 +1400,24 @@ is_digit_8(X) when X =< 16#0669, X > (16#0660-1) -> true; is_digit_8(16#0670) -> false; is_digit_8(_) -> false. +is_digit_9(A, 0) when A =:= 42 -> false; +is_digit_9(_, X) when X > 16#065F, X < 16#066A -> true; +is_digit_9(_, X) when 16#0030 =< X, X =< 16#0039 -> true; +is_digit_9(_, X) when 16#06F0 =< X, X =< 16#06F9 -> true; +is_digit_9(_, _) -> false. + +is_digit_10(0, 0) -> false; +is_digit_10(X, _) when X < 16#066A, 16#0660 =< X -> true; +is_digit_10(X, _) when 16#0030 =< X, X =< 16#0039 -> true; +is_digit_10(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true; +is_digit_10(_, _) -> false. + +is_digit_11(0, 0) -> false; +is_digit_11(X, _) when X =< 16#0669, 16#0660 =< X -> true; +is_digit_11(X, _) when 16#0030 =< X, X =< 16#0039 -> true; +is_digit_11(X, _) when 16#06F0 =< X, X =< 16#06F9 -> true; +is_digit_11(_, _) -> false. + rel_op_combinations_2(0, _) -> ok; rel_op_combinations_2(N, Range) -> @@ -1471,6 +1518,7 @@ rel_op_combinations_3(N, Red) -> Val = redundant_9(N), Val = redundant_10(N), Val = redundant_11(N), + Val = redundant_11(N), rel_op_combinations_3(N-1, Red). redundant_1(X) when X >= 51, X =< 80 -> 5*X; @@ -1525,6 +1573,10 @@ redundant_11(X) when X =:= 10 -> 2*X; redundant_11(X) when X >= 51, X =< 80 -> 5*X; redundant_11(_) -> none. +redundant_12(X) when X >= 50, X =< 80 -> 2*X; +redundant_12(X) when X < 51 -> 5*X; +redundant_12(_) -> none. + %% Test type tests on literal values. (From emulator test suites.) literal_type_tests(Config) when is_list(Config) -> case ?MODULE of diff --git a/lib/compiler/test/map_SUITE.erl b/lib/compiler/test/map_SUITE.erl index 3e0ab78390..440b632381 100644 --- a/lib/compiler/test/map_SUITE.erl +++ b/lib/compiler/test/map_SUITE.erl @@ -70,7 +70,10 @@ t_bad_update/1, %% new in OTP 21 - t_reused_key_variable/1 + t_reused_key_variable/1, + + %% new in OTP 22 + t_mixed_clause/1,cover_beam_trim/1 ]). suite() -> []. @@ -124,7 +127,10 @@ all() -> t_bad_update, %% new in OTP 21 - t_reused_key_variable + t_reused_key_variable, + + %% new in OTP 22 + t_mixed_clause,cover_beam_trim ]. groups() -> []. @@ -1373,22 +1379,22 @@ map_usage(Def, Used) -> t_guard_sequence(Config) when is_list(Config) -> - {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}), - {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}), - {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}), - {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}), - {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}), - - {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})), - {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})), - {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})), - {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})), - {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})), - - %% error case - {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})), - {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})), - ok. + {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>id("a")}), + {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>id("b")}), + {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>id("c")}), + {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>id("d")}), + {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>id("e")}), + + {1,M1} = map_guard_sequence_2(M1 = id(#{a=>3})), + {2,M2} = map_guard_sequence_2(M2 = id(#{a=>4, b=>4})), + {3,gg,M3} = map_guard_sequence_2(M3 = id(#{a=>gg, b=>4})), + {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(#{a=>sc, b=>3, c=>sc2})), + {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(#{a=>kk, b=>other, c=>sc2})), + + %% error case + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>id("e")})), + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})), + ok. t_guard_sequence_large(Config) when is_list(Config) -> M0 = id(#{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00",03]}=>"10", @@ -1443,21 +1449,21 @@ t_guard_sequence_large(Config) when is_list(Config) -> 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18", 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" }), - {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>id("a")}), - {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>id("b")}), - {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>id("c")}), - {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>id("d")}), - {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>id("e")}), + {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>id("a")}), + {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>id("b")}), + {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>id("c")}), + {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>id("d")}), + {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>id("e")}), - {1,M1} = map_guard_sequence_2(M1 = id(M0#{a=>3})), - {2,M2} = map_guard_sequence_2(M2 = id(M0#{a=>4, b=>4})), - {3,gg,M3} = map_guard_sequence_2(M3 = id(M0#{a=>gg, b=>4})), - {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(M0#{a=>sc, b=>3, c=>sc2})), - {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(M0#{a=>kk, b=>other, c=>sc2})), + {1,M1} = map_guard_sequence_2(M1 = id(M0#{a=>3})), + {2,M2} = map_guard_sequence_2(M2 = id(M0#{a=>4, b=>4})), + {3,gg,M3} = map_guard_sequence_2(M3 = id(M0#{a=>gg, b=>4})), + {4,sc,sc,M4} = map_guard_sequence_2(M4 = id(M0#{a=>sc, b=>3, c=>sc2})), + {5,kk,kk,M5} = map_guard_sequence_2(M5 = id(M0#{a=>kk, b=>other, c=>sc2})), - {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>id("e")})), - {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})), - ok. + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>id("e")})), + {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})), + ok. map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val}; map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val}; @@ -2079,7 +2085,7 @@ t_register_corruption(Config) when is_list(Config) -> {3,wanted,<<"value">>} = register_corruption_foo(wanted,M), ok. -register_corruption_foo(A,#{a := V1, b := V2}) -> +register_corruption_foo(_,#{a := V1, b := V2}) -> register_corruption_dummy_call(1,V1,V2); register_corruption_foo(A,#{b := V}) -> register_corruption_dummy_call(2,A,V); @@ -2161,6 +2167,31 @@ t_reused_key_variable(Config) when is_list(Config) -> ok end. +t_mixed_clause(_Config) -> + put(fool_inliner, x), + K = get(fool_inliner), + {42,100} = case #{K=>42,y=>100} of + #{x:=X,y:=Y} -> + {X,Y} + end, + nomatch = case #{K=>42,y=>100} of + #{x:=X,y:=0} -> + {X,Y}; + #{} -> + nomatch + end, + ok. + +cover_beam_trim(_Config) -> + val = do_cover_beam_trim(id, max, max, id, #{id=>val}), + ok. + +do_cover_beam_trim(Id, OldMax, Max, Id, M) -> + OldMax = id(Max), + #{Id:=Val} = id(M), + Val. + + %% aux rand_terms(0) -> []; diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index 229c3093d7..60ab969929 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -25,7 +25,7 @@ match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1, selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1, coverage/1,grab_bag/1,literal_binary/1, - unary_op/1]). + unary_op/1,eq_types/1]). -include_lib("common_test/include/ct.hrl"). @@ -40,7 +40,7 @@ groups() -> match_in_call,untuplify, shortcut_boolean,letify_guard,selectify,deselectify, underscore,match_map,map_vars_used,coverage, - grab_bag,literal_binary,unary_op]}]. + grab_bag,literal_binary,unary_op,eq_types]}]. init_per_suite(Config) -> @@ -390,6 +390,13 @@ untuplify(Config) when is_list(Config) -> %% We do this to cover sys_core_fold:unalias_pat/1. {1,2,3,4,alias,{[1,2],{3,4},alias}} = untuplify_1([1,2], {3,4}, alias), error = untuplify_1([1,2], {3,4}, 42), + + %% Test that a previous bug in v3_codegen is gone. (The sinking of + %% stack frames into only the case arms that needed them was not always + %% safe.) + [33, -1, -33, 1] = untuplify_2(32, 65), + {33, 1, -33, -1} = untuplify_2(65, 32), + ok. untuplify_1(A, B, C) -> @@ -402,6 +409,21 @@ untuplify_1(A, B, C) -> error end. +untuplify_2(V1, V2) -> + {D1,D2,D3,D4} = + if V1 > V2 -> + %% The 1 value was overwritten by the value of V2-V1. + {V1-V2, 1, V2-V1, -1}; + true -> + {V2-V1, -1, V1-V2, 1} + end, + if + D2 > D4 -> + {D1, D2, D3, D4}; + true -> + [D1, D2, D3, D4] + end. + %% Coverage of beam_dead:shortcut_boolean_label/4. shortcut_boolean(Config) when is_list(Config) -> false = shortcut_boolean_1([0]), @@ -483,9 +505,8 @@ sel_same_value2(V) when V =:= 42; V =:= 43 -> sel_same_value2(_) -> error. -%% Test deconstruction of select_val instructions in beam_peep into -%% regular tests with just one possible value left. Hitting proper cases -%% in beam_peep relies on unification of labels by beam_jump. +%% Test deconstruction of select_val instructions to regular tests +%% with zero or one values left. deselectify(Config) when is_list(Config) -> one_or_other = desel_tuple_arity({1}), @@ -506,7 +527,31 @@ deselectify(Config) when is_list(Config) -> one_or_other = dsel_atom_typecheck(one), two = dsel_atom_typecheck(two), - one_or_other = dsel_atom_typecheck(three). + one_or_other = dsel_atom_typecheck(three), + + %% Cover deconstruction of select_val instructions in + %% beam_peep. + + stop = dsel_peek_0(stop), + ignore = dsel_peek_0(ignore), + Config = dsel_peek_0(Config), + + stop = dsel_peek_1(stop, any), + Config = dsel_peek_1(ignore, Config), + other = dsel_peek_1(other, ignored), + + 0 = dsel_peek_2(0, any), + Config = dsel_peek_2(1, Config), + 2 = dsel_peek_2(2, ignored), + + true = dsel_peek_3(true), + false = dsel_peek_3(false), + {error,Config} = dsel_peek_3(Config), + + ok. + +%% The following will be optimized by the sharing optimizations +%% in beam_ssa_opt. desel_tuple_arity(Tuple) when is_tuple(Tuple) -> case Tuple of @@ -543,6 +588,39 @@ dsel_atom_typecheck(Val) when is_atom(Val) -> _ -> one_or_other end. +%% The following functions are carefully crafted so that the sharing +%% optimizations in beam_ssa_opt can't be applied. After applying the +%% beam_jump:eliminate_moves/1 optimization and beam_clean:clean_labels/1 +%% has unified labels, beam_peep is able to optimize these functions. + +dsel_peek_0(A0) -> + case id(A0) of + stop -> stop; + ignore -> ignore; + A -> A + end. + +dsel_peek_1(A0, B) -> + case id(A0) of + stop -> stop; + ignore -> B; + A -> A + end. + +dsel_peek_2(A0, B) -> + case id(A0) of + 0 -> 0; + 1 -> B; + A -> A + end. + +dsel_peek_3(A0) -> + case id(A0) of + true -> true; + false -> false; + Other -> {error,Other} + end. + underscore(Config) when is_list(Config) -> case Config of [] -> @@ -585,13 +663,26 @@ do_map_vars_used(X, Y, Map) -> Val end. +-record(coverage_id, {bool=false,id}). coverage(Config) when is_list(Config) -> %% Cover beam_dead. ok = coverage_1(x, a), ok = coverage_1(x, b), %% Cover sys_pre_expand. - ok = coverage_3("abc"). + ok = coverage_3("abc"), + + %% Cover beam_ssa_dead. + {expr,key} = coverage_4([literal,get], [[expr,key]]), + {expr,key} = coverage_4([expr,key], []), + + a = coverage_5([8,8,8], #coverage_id{bool=true}), + b = coverage_5([], #coverage_id{bool=true}), + + %% Cover beam_ssa_opt. + ok = coverage_6(), + + ok. coverage_1(B, Tag) -> case Tag of @@ -604,6 +695,37 @@ coverage_2(2, b, x) -> ok. coverage_3([$a]++[]++"bc") -> ok. +%% Cover beam_ssa_dead:eval_type_test_1(is_nonempty_list, Arg). +coverage_4([literal,get], [Expr]) -> + coverage_4(Expr, []); +coverage_4([Expr,Key], []) -> + {Expr,Key}. + +%% Cover beam_ssa_dead:eval_type_test_1(is_tagged_tuple, Arg). +coverage_5(Config, TermId) + when TermId =:= #coverage_id{bool=true}, + Config =:= [8,8,8] -> + a; +coverage_5(_Config, #coverage_id{bool=true}) -> + b. + +coverage_6() -> + X = 17, + case + case id(1) > 0 of + true -> + 17; + false -> + 42 + end + of + X -> + ok; + V -> + %% Cover beam_ssa_opt:make_literal/2. + error([error,X,V]) + end. + grab_bag(_Config) -> [_|T] = id([a,b,c]), [b,c] = id(T), @@ -748,5 +870,25 @@ unary_op_1(Vop@1) -> end end. +eq_types(_Config) -> + Ref = make_ref(), + Ref = eq_types(Ref, any), + ok. + +eq_types(A, B) -> + %% {put_tuple2,{y,0},{list,[{x,0},{x,1}]}}. + Term0 = {A, B}, + Term = id(Term0), + + %% {test,is_eq_exact,{f,3},[{y,0},{x,0}]}. + %% Here beam_validator must infer that {x,0} has the + %% same type as {y,0}. + Term = Term0, + + %% {get_tuple_element,{x,0},0,{x,0}}. + {Ref22,_} = Term, + + Ref22. + id(I) -> I. diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index d6fc51448f..e999c8ffae 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -183,6 +183,7 @@ silly_coverage(Config) when is_list(Config) -> %% beam_ssa_lint %% beam_ssa_recv + %% beam_ssa_share %% beam_ssa_pre_codegen %% beam_ssa_opt %% beam_ssa_codegen @@ -190,6 +191,7 @@ silly_coverage(Config) when is_list(Config) -> [{b_function,#{func_info=>{mod,foo,0}},args,bad_blocks,0}]}, expect_error(fun() -> beam_ssa_lint:module(BadSSA, []) end), expect_error(fun() -> beam_ssa_recv:module(BadSSA, []) end), + expect_error(fun() -> beam_ssa_share:module(BadSSA, []) end), expect_error(fun() -> beam_ssa_pre_codegen:module(BadSSA, []) end), expect_error(fun() -> beam_ssa_opt:module(BadSSA, []) end), expect_error(fun() -> beam_ssa_codegen:module(BadSSA, []) end), @@ -221,10 +223,6 @@ silly_coverage(Config) when is_list(Config) -> {label,2}|non_proper_list]}],99}, expect_error(fun() -> beam_block:module(BlockInput, []) end), - %% beam_bs - BsInput = BlockInput, - expect_error(fun() -> beam_bs:module(BsInput, []) end), - %% beam_except ExceptInput = {?MODULE,[{foo,0}],[], [{function,foo,0,2, @@ -258,7 +256,7 @@ silly_coverage(Config) when is_list(Config) -> [{function,foo,0,2, [{label,1}, {func_info,{atom,?MODULE},{atom,foo},0}, - {label,2},{select,op,r,{f,2},[{f,2}]}]}], + {label,2},{select,select_val,r,{f,2},[{f,2}]}]}], 2}, expect_error(fun() -> beam_peep:module(PeepInput, []) end), diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl index 4219768d6f..12108445f0 100644 --- a/lib/compiler/test/receive_SUITE.erl +++ b/lib/compiler/test/receive_SUITE.erl @@ -25,7 +25,7 @@ init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2, export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1, - wait/1,recv_in_try/1,double_recv/1]). + wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1]). -include_lib("common_test/include/ct.hrl"). @@ -45,7 +45,7 @@ all() -> groups() -> [{p,test_lib:parallel(), [recv,coverage,otp_7980,ref_opt,export,wait, - recv_in_try,double_recv]}]. + recv_in_try,double_recv,receive_var_zero]}]. init_per_suite(Config) -> @@ -378,4 +378,27 @@ do_double_recv(_, Msg) -> error end. +%% Test 'after Z', when Z =:= 0 been propagated as an immediate by the type +%% optimization pass. +receive_var_zero(Config) when is_list(Config) -> + self() ! x, + self() ! y, + Z = zero(), + timeout = receive + z -> ok + after Z -> timeout + end, + timeout = receive + after Z -> timeout + end, + self() ! w, + receive + x -> ok; + Other -> + ct:fail({bad_message,Other}) + end. + +zero() -> 0. + + id(I) -> I. diff --git a/lib/compiler/test/regressions_SUITE.erl b/lib/compiler/test/regressions_SUITE.erl index 9b0b9b0c38..39febf060f 100644 --- a/lib/compiler/test/regressions_SUITE.erl +++ b/lib/compiler/test/regressions_SUITE.erl @@ -23,7 +23,7 @@ -export([all/0,groups/0,init_per_testcase/2,end_per_testcase/2, init_per_group/2,end_per_group/2, - init_per_testcase/2,end_per_testcase/2, + init_per_suite/1,end_per_suite/1, suite/0]). -export([maps/1]). diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl index 4502f5b68a..26149e11e6 100644 --- a/lib/compiler/test/test_lib.erl +++ b/lib/compiler/test/test_lib.erl @@ -81,6 +81,8 @@ opt_opts(Mod) -> (no_put_tuple2) -> true; (no_bsm3) -> true; (no_bsm_opt) -> true; + (no_module_opt) -> true; + (no_type_opt) -> true; (_) -> false end, Opts). @@ -93,8 +95,9 @@ get_data_dir(Config) -> Opts = [{return,list}], Data1 = re:replace(Data0, "_no_opt_SUITE", "_SUITE", Opts), Data2 = re:replace(Data1, "_post_opt_SUITE", "_SUITE", Opts), - Data = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts), - re:replace(Data, "_r21_SUITE", "_SUITE", Opts). + Data3 = re:replace(Data2, "_inline_SUITE", "_SUITE", Opts), + Data4 = re:replace(Data3, "_r21_SUITE", "_SUITE", Opts), + re:replace(Data4, "_no_module_opt_SUITE", "_SUITE", Opts). is_cloned_mod(Mod) -> is_cloned_mod_1(atom_to_list(Mod)). @@ -105,6 +108,7 @@ is_cloned_mod_1("no_opt_SUITE") -> true; is_cloned_mod_1("post_opt_SUITE") -> true; is_cloned_mod_1("inline_SUITE") -> true; is_cloned_mod_1("21_SUITE") -> true; +is_cloned_mod_1("no_module_opt_SUITE") -> true; is_cloned_mod_1([_|T]) -> is_cloned_mod_1(T); is_cloned_mod_1([]) -> false. diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl index 1b7ef4ddb0..8f9cd9ab1e 100644 --- a/lib/compiler/test/trycatch_SUITE.erl +++ b/lib/compiler/test/trycatch_SUITE.erl @@ -1189,7 +1189,8 @@ bad_raise(Expr) -> test_raise(Expr) -> test_raise_1(Expr), test_raise_2(Expr), - test_raise_3(Expr). + test_raise_3(Expr), + test_raise_4(Expr). test_raise_1(Expr) -> erase(exception), @@ -1263,5 +1264,28 @@ do_test_raise_3(Expr) -> erlang:raise(exit, {exception,C,E}, Stk) end. +test_raise_4(Expr) -> + try + do_test_raise_4(Expr) + catch + exit:{exception,C,E,Stk}:Stk -> + try + Expr() + catch + C:E:S -> + [StkTop|_] = S, + [StkTop|_] = Stk + end + end. + +do_test_raise_4(Expr) -> + try + Expr() + catch + C:E:Stk -> + %% Here the stacktrace must be built. + erlang:raise(exit, {exception,C,E,Stk}, Stk) + end. + id(I) -> I. diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 92f8aec424..efedb414ad 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.2.7 +COMPILER_VSN = 7.3.1 diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in index cd0e5442e9..508e1c40ee 100644 --- a/lib/crypto/c_src/Makefile.in +++ b/lib/crypto/c_src/Makefile.in @@ -71,7 +71,35 @@ PRIVDIR = ../priv OBJDIR = $(PRIVDIR)/obj/$(TARGET) LIBDIR = $(PRIVDIR)/lib/$(TARGET) -CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o +CRYPTO_OBJS = $(OBJDIR)/crypto$(TYPEMARKER).o \ + $(OBJDIR)/aead$(TYPEMARKER).o \ + $(OBJDIR)/aes$(TYPEMARKER).o \ + $(OBJDIR)/algorithms$(TYPEMARKER).o \ + $(OBJDIR)/atoms$(TYPEMARKER).o \ + $(OBJDIR)/block$(TYPEMARKER).o \ + $(OBJDIR)/bn$(TYPEMARKER).o \ + $(OBJDIR)/chacha20$(TYPEMARKER).o \ + $(OBJDIR)/cipher$(TYPEMARKER).o \ + $(OBJDIR)/cmac$(TYPEMARKER).o \ + $(OBJDIR)/dh$(TYPEMARKER).o \ + $(OBJDIR)/digest$(TYPEMARKER).o \ + $(OBJDIR)/dss$(TYPEMARKER).o \ + $(OBJDIR)/ec$(TYPEMARKER).o \ + $(OBJDIR)/ecdh$(TYPEMARKER).o \ + $(OBJDIR)/eddsa$(TYPEMARKER).o \ + $(OBJDIR)/engine$(TYPEMARKER).o \ + $(OBJDIR)/evp$(TYPEMARKER).o \ + $(OBJDIR)/fips$(TYPEMARKER).o \ + $(OBJDIR)/hash$(TYPEMARKER).o \ + $(OBJDIR)/hmac$(TYPEMARKER).o \ + $(OBJDIR)/info$(TYPEMARKER).o \ + $(OBJDIR)/math$(TYPEMARKER).o \ + $(OBJDIR)/pkey$(TYPEMARKER).o \ + $(OBJDIR)/poly1305$(TYPEMARKER).o \ + $(OBJDIR)/rand$(TYPEMARKER).o \ + $(OBJDIR)/rc4$(TYPEMARKER).o \ + $(OBJDIR)/rsa$(TYPEMARKER).o \ + $(OBJDIR)/srp$(TYPEMARKER).o CALLBACK_OBJS = $(OBJDIR)/crypto_callback$(TYPEMARKER).o NIF_MAKEFILE = $(PRIVDIR)/Makefile CRYPTO_STATIC_OBJS = $(OBJDIR)/crypto_static$(TYPEMARKER).o\ @@ -172,24 +200,21 @@ $(LIBDIR)/crypto_callback$(TYPEMARKER).dll: $(CALLBACK_OBJS) endif -clean: - rm -f $(LIBDIR)/crypto.@DED_EXT@ - rm -f $(LIBDIR)/crypto.debug.@DED_EXT@ - rm -f $(LIBDIR)/crypto.valgrind.@DED_EXT@ - rm -f $(LIBDIR)/crypto_callback.@DED_EXT@ - rm -f $(LIBDIR)/crypto_callback.debug.@DED_EXT@ - rm -f $(LIBDIR)/crypto_callback.valgrind.@DED_EXT@ - rm -f $(LIBDIR)/otp_test_engine.@DED_EXT@ - rm -f $(OBJDIR)/crypto.o - rm -f $(OBJDIR)/crypto_static.o - rm -f $(OBJDIR)/crypto.debug.o - rm -f $(OBJDIR)/crypto_static.debug.o - rm -f $(OBJDIR)/crypto.valgrind.o - rm -f $(OBJDIR)/crypto_static.valgrind.o - rm -f $(OBJDIR)/crypto_callback.o - rm -f $(OBJDIR)/crypto_callback.debug.o - rm -f $(OBJDIR)/crypto_callback.valgrind.o - rm -f $(OBJDIR)/otp_test_engine.o +CLEAN_OBJS_RAW = $(CRYPTO_OBJS) $(CALLBACK_OBJS) $(CRYPTO_STATIC_OBJS) $(TEST_ENGINE_OBJS) +CLEAN_OBJS_O = $(patsubst %.debug.o,%.o,$(CLEAN_OBJS_RAW:.valgrind.o=.o)) + +CLEAN_LIBS_RAW = $(NIF_LIB) $(CALLBACK_LIB) $(TEST_ENGINE_LIB) +CLEAN_LIBS_SO = $(patsubst %.debug.@DED_EXT@,%.@DED_EXT@,$(CLEAN_LIBS_RAW:.valgrind.@DED_EXT@=.@DED_EXT@)) + +clean_dynamic_libs: + rm -f $(CLEAN_LIBS_SO) + rm -f $(foreach T,.valgrind.@DED_EXT@ .debug.@DED_EXT@,$(CLEAN_LIBS_SO:.@DED_EXT@=$T)) + +clean_objs: + rm -f $(CLEAN_OBJS_O) + rm -f $(foreach T,.valgrind.o .debug.o,$(CLEAN_OBJS_O:.o=$T)) + +clean: clean_objs clean_dynamic_libs rm -f core *~ docs: diff --git a/lib/crypto/c_src/aead.c b/lib/crypto/c_src/aead.c new file mode 100644 index 0000000000..b7ed06e3bc --- /dev/null +++ b/lib/crypto/c_src/aead.c @@ -0,0 +1,233 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "aead.h" +#include "aes.h" + +ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type,Key,Iv,AAD,In) */ +#if defined(HAVE_AEAD) + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher = NULL; + ErlNifBinary key, iv, aad, in; + unsigned int tag_len; + unsigned char *outp, *tagp; + ERL_NIF_TERM type, out, out_tag; + int len, ctx_ctrl_set_ivlen, ctx_ctrl_get_tag; + + type = argv[0]; + + if (!enif_is_atom(env, type) + || !enif_inspect_iolist_as_binary(env, argv[1], &key) + || !enif_inspect_binary(env, argv[2], &iv) + || !enif_inspect_iolist_as_binary(env, argv[3], &aad) + || !enif_inspect_iolist_as_binary(env, argv[4], &in) + || !enif_get_uint(env, argv[5], &tag_len)) { + return enif_make_badarg(env); + } + + /* Use cipher_type some day. Must check block_encrypt|decrypt first */ +#if defined(HAVE_GCM) + if (type == atom_aes_gcm) { + if ((iv.size > 0) + && (1 <= tag_len && tag_len <= 16)) { + ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN; + ctx_ctrl_get_tag = EVP_CTRL_GCM_GET_TAG; + if (key.size == 16) cipher = EVP_aes_128_gcm(); + else if (key.size == 24) cipher = EVP_aes_192_gcm(); + else if (key.size == 32) cipher = EVP_aes_256_gcm(); + else enif_make_badarg(env); + } else + enif_make_badarg(env); + } else +#endif +#if defined(HAVE_CCM) + if (type == atom_aes_ccm) { + if ((7 <= iv.size && iv.size <= 13) + && (4 <= tag_len && tag_len <= 16) + && ((tag_len & 1) == 0) + ) { + ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN; + ctx_ctrl_get_tag = EVP_CTRL_CCM_GET_TAG; + if (key.size == 16) cipher = EVP_aes_128_ccm(); + else if (key.size == 24) cipher = EVP_aes_192_ccm(); + else if (key.size == 32) cipher = EVP_aes_256_ccm(); + else enif_make_badarg(env); + } else + enif_make_badarg(env); + } else +#endif +#if defined(HAVE_CHACHA20_POLY1305) + if (type == atom_chacha20_poly1305) { + if ((key.size == 32) + && (1 <= iv.size && iv.size <= 16) + && (tag_len == 16) + ) { + ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN; + ctx_ctrl_get_tag = EVP_CTRL_AEAD_GET_TAG, + cipher = EVP_chacha20_poly1305(); + } else enif_make_badarg(env); + } else +#endif + return enif_raise_exception(env, atom_notsup); + + ctx = EVP_CIPHER_CTX_new(); + if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err; + if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err; + +#if defined(HAVE_CCM) + if (type == atom_aes_ccm) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL) != 1) goto out_err; + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err; + if (EVP_EncryptUpdate(ctx, NULL, &len, NULL, in.size) != 1) goto out_err; + } else +#endif + if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err; + + if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err; + + outp = enif_make_new_binary(env, in.size, &out); + + if (EVP_EncryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err; + if (EVP_EncryptFinal_ex(ctx, outp/*+len*/, &len) != 1) goto out_err; + + tagp = enif_make_new_binary(env, tag_len, &out_tag); + + if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_get_tag, tag_len, tagp) != 1) goto out_err; + + EVP_CIPHER_CTX_free(ctx); + CONSUME_REDS(env, in); + return enif_make_tuple2(env, out, out_tag); + +out_err: + EVP_CIPHER_CTX_free(ctx); + return atom_error; + +#else + return enif_raise_exception(env, atom_notsup); +#endif +} + +ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type,Key,Iv,AAD,In,Tag) */ +#if defined(HAVE_AEAD) + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher = NULL; + ErlNifBinary key, iv, aad, in, tag; + unsigned char *outp; + ERL_NIF_TERM type, out; + int len, ctx_ctrl_set_ivlen, ctx_ctrl_set_tag; + + type = argv[0]; +#if defined(HAVE_GCM_EVP_DECRYPT_BUG) + if (type == atom_aes_gcm) + return aes_gcm_decrypt_NO_EVP(env, argc, argv); +#endif + + if (!enif_is_atom(env, type) + || !enif_inspect_iolist_as_binary(env, argv[1], &key) + || !enif_inspect_binary(env, argv[2], &iv) + || !enif_inspect_iolist_as_binary(env, argv[3], &aad) + || !enif_inspect_iolist_as_binary(env, argv[4], &in) + || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) { + return enif_make_badarg(env); + } + + /* Use cipher_type some day. Must check block_encrypt|decrypt first */ +#if defined(HAVE_GCM) + if (type == atom_aes_gcm) { + if (iv.size > 0) { + ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN; + ctx_ctrl_set_tag = EVP_CTRL_GCM_SET_TAG; + if (key.size == 16) cipher = EVP_aes_128_gcm(); + else if (key.size == 24) cipher = EVP_aes_192_gcm(); + else if (key.size == 32) cipher = EVP_aes_256_gcm(); + else enif_make_badarg(env); + } else + enif_make_badarg(env); + } else +#endif +#if defined(HAVE_CCM) + if (type == atom_aes_ccm) { + if (iv.size > 0) { + ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN; + if (key.size == 16) cipher = EVP_aes_128_ccm(); + else if (key.size == 24) cipher = EVP_aes_192_ccm(); + else if (key.size == 32) cipher = EVP_aes_256_ccm(); + else enif_make_badarg(env); + } else + enif_make_badarg(env); + } else +#endif +#if defined(HAVE_CHACHA20_POLY1305) + if (type == atom_chacha20_poly1305) { + if ((key.size == 32) + && (1 <= iv.size && iv.size <= 16) + && tag.size == 16 + ) { + ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN; + ctx_ctrl_set_tag = EVP_CTRL_AEAD_SET_TAG; + cipher = EVP_chacha20_poly1305(); + } else enif_make_badarg(env); + } else +#endif + return enif_raise_exception(env, atom_notsup); + + outp = enif_make_new_binary(env, in.size, &out); + + ctx = EVP_CIPHER_CTX_new(); + if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err; + if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err; + +#if defined(HAVE_CCM) + if (type == atom_aes_ccm) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag.size, tag.data) != 1) goto out_err; + } +#endif + + if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err; + +#if defined(HAVE_CCM) + if (type == atom_aes_ccm) { + if (1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, in.size)) goto out_err; + } +#endif + + if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err; + if (EVP_DecryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err; + +#if defined(HAVE_GCM) || defined(HAVE_CHACHA20_POLY1305) + if (type == atom_aes_gcm) { + if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, tag.size, tag.data) != 1) goto out_err; + if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1) goto out_err; + } +#endif + EVP_CIPHER_CTX_free(ctx); + + CONSUME_REDS(env, in); + return out; + +out_err: + EVP_CIPHER_CTX_free(ctx); + return atom_error; +#else + return enif_raise_exception(env, atom_notsup); +#endif +} diff --git a/lib/crypto/c_src/aead.h b/lib/crypto/c_src/aead.h new file mode 100644 index 0000000000..54c0711535 --- /dev/null +++ b/lib/crypto/c_src/aead.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_AEAD_H__ +#define E_AEAD_H__ 1 + +#include "common.h" + +ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_AEAD_H__ */ diff --git a/lib/crypto/c_src/aes.c b/lib/crypto/c_src/aes.c new file mode 100644 index 0000000000..36cd02933f --- /dev/null +++ b/lib/crypto/c_src/aes.c @@ -0,0 +1,282 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "aes.h" +#include "cipher.h" + +ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, IVec, Data, IsEncrypt) */ + ErlNifBinary key, ivec, text; + AES_KEY aes_key; + unsigned char ivec_clone[16]; /* writable copy */ + int new_ivlen = 0; + ERL_NIF_TERM ret; + + CHECK_NO_FIPS_MODE(); + + if (!enif_inspect_iolist_as_binary(env, argv[0], &key) + || !(key.size == 16 || key.size == 24 || key.size == 32) + || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16 + || !enif_inspect_iolist_as_binary(env, argv[2], &text)) { + return enif_make_badarg(env); + } + + memcpy(ivec_clone, ivec.data, 16); + AES_set_encrypt_key(key.data, key.size * 8, &aes_key); + AES_cfb8_encrypt((unsigned char *) text.data, + enif_make_new_binary(env, text.size, &ret), + text.size, &aes_key, ivec_clone, &new_ivlen, + (argv[3] == atom_true)); + CONSUME_REDS(env,text); + return ret; +} + +ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, IVec, Data, IsEncrypt) */ + ErlNifBinary key, ivec, text; + AES_KEY aes_key; + unsigned char ivec_clone[16]; /* writable copy */ + int new_ivlen = 0; + ERL_NIF_TERM ret; + + if (!enif_inspect_iolist_as_binary(env, argv[0], &key) + || !(key.size == 16 || key.size == 24 || key.size == 32) + || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16 + || !enif_inspect_iolist_as_binary(env, argv[2], &text)) { + return enif_make_badarg(env); + } + + memcpy(ivec_clone, ivec.data, 16); + AES_set_encrypt_key(key.data, key.size * 8, &aes_key); + AES_cfb128_encrypt((unsigned char *) text.data, + enif_make_new_binary(env, text.size, &ret), + text.size, &aes_key, ivec_clone, &new_ivlen, + (argv[3] == atom_true)); + CONSUME_REDS(env,text); + return ret; +} + +ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, IVec, Data, IsEncrypt) */ +#ifdef HAVE_AES_IGE + ErlNifBinary key_bin, ivec_bin, data_bin; + AES_KEY aes_key; + unsigned char ivec[32]; + int i; + unsigned char* ret_ptr; + ERL_NIF_TERM ret; + + CHECK_NO_FIPS_MODE(); + + if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) + || (key_bin.size != 16 && key_bin.size != 32) + || !enif_inspect_binary(env, argv[1], &ivec_bin) + || ivec_bin.size != 32 + || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin) + || data_bin.size % 16 != 0) { + + return enif_make_badarg(env); + } + + if (argv[3] == atom_true) { + i = AES_ENCRYPT; + AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key); + } + else { + i = AES_DECRYPT; + AES_set_decrypt_key(key_bin.data, key_bin.size*8, &aes_key); + } + + ret_ptr = enif_make_new_binary(env, data_bin.size, &ret); + memcpy(ivec, ivec_bin.data, 32); /* writable copy */ + AES_ige_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, i); + CONSUME_REDS(env,data_bin); + return ret; +#else + return atom_notsup; +#endif +} + + +/* Initializes state for ctr streaming (de)encryption +*/ +#ifdef HAVE_EVP_AES_CTR +ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, IVec) */ + ErlNifBinary key_bin, ivec_bin; + struct evp_cipher_ctx *ctx; + const EVP_CIPHER *cipher; + ERL_NIF_TERM ret; + + if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) + || !enif_inspect_binary(env, argv[1], &ivec_bin) + || ivec_bin.size != 16) { + return enif_make_badarg(env); + } + + switch (key_bin.size) + { + case 16: cipher = EVP_aes_128_ctr(); break; + case 24: cipher = EVP_aes_192_ctr(); break; + case 32: cipher = EVP_aes_256_ctr(); break; + default: return enif_make_badarg(env); + } + + ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); + ctx->ctx = EVP_CIPHER_CTX_new(); + EVP_CipherInit_ex(ctx->ctx, cipher, NULL, + key_bin.data, ivec_bin.data, 1); + EVP_CIPHER_CTX_set_padding(ctx->ctx, 0); + ret = enif_make_resource(env, ctx); + enif_release_resource(ctx); + return ret; +} + +ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Context, Data) */ + struct evp_cipher_ctx *ctx, *new_ctx; + ErlNifBinary data_bin; + ERL_NIF_TERM ret, cipher_term; + unsigned char *out; + int outl = 0; + + if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx) + || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) { + return enif_make_badarg(env); + } + new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); + new_ctx->ctx = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx); + out = enif_make_new_binary(env, data_bin.size, &cipher_term); + EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size); + ASSERT(outl == data_bin.size); + + ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term); + enif_release_resource(new_ctx); + CONSUME_REDS(env,data_bin); + return ret; +} + +#else /* if not HAVE_EVP_AES_CTR */ + +ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, IVec) */ + ErlNifBinary key_bin, ivec_bin; + ERL_NIF_TERM ecount_bin; + + if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) + || !enif_inspect_binary(env, argv[1], &ivec_bin) + || !(key_bin.size == 16 || key_bin.size == 24 || key_bin.size ==32) + || ivec_bin.size != 16) { + return enif_make_badarg(env); + } + + memset(enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin), + 0, AES_BLOCK_SIZE); + return enif_make_tuple4(env, argv[0], argv[1], ecount_bin, enif_make_int(env, 0)); +} + +ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* ({Key, IVec, ECount, Num}, Data) */ + ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin; + AES_KEY aes_key; + unsigned int num; + ERL_NIF_TERM ret, num2_term, cipher_term, ivec2_term, ecount2_term, new_state_term; + int state_arity; + const ERL_NIF_TERM *state_term; + unsigned char * ivec2_buf; + unsigned char * ecount2_buf; + + if (!enif_get_tuple(env, argv[0], &state_arity, &state_term) + || state_arity != 4 + || !enif_inspect_iolist_as_binary(env, state_term[0], &key_bin) + || AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key) != 0 + || !enif_inspect_binary(env, state_term[1], &ivec_bin) || ivec_bin.size != 16 + || !enif_inspect_binary(env, state_term[2], &ecount_bin) || ecount_bin.size != AES_BLOCK_SIZE + || !enif_get_uint(env, state_term[3], &num) + || !enif_inspect_iolist_as_binary(env, argv[1], &text_bin)) { + return enif_make_badarg(env); + } + + ivec2_buf = enif_make_new_binary(env, ivec_bin.size, &ivec2_term); + ecount2_buf = enif_make_new_binary(env, ecount_bin.size, &ecount2_term); + + memcpy(ivec2_buf, ivec_bin.data, 16); + memcpy(ecount2_buf, ecount_bin.data, ecount_bin.size); + + AES_ctr128_encrypt((unsigned char *) text_bin.data, + enif_make_new_binary(env, text_bin.size, &cipher_term), + text_bin.size, &aes_key, ivec2_buf, ecount2_buf, &num); + + num2_term = enif_make_uint(env, num); + new_state_term = enif_make_tuple4(env, state_term[0], ivec2_term, ecount2_term, num2_term); + ret = enif_make_tuple2(env, new_state_term, cipher_term); + CONSUME_REDS(env,text_bin); + return ret; +} +#endif /* !HAVE_EVP_AES_CTR */ + +#ifdef HAVE_GCM_EVP_DECRYPT_BUG +ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type,Key,Iv,AAD,In,Tag) */ + GCM128_CONTEXT *ctx; + ErlNifBinary key, iv, aad, in, tag; + AES_KEY aes_key; + unsigned char *outp; + ERL_NIF_TERM out; + + if (!enif_inspect_iolist_as_binary(env, argv[1], &key) + || AES_set_encrypt_key(key.data, key.size*8, &aes_key) != 0 + || !enif_inspect_binary(env, argv[2], &iv) || iv.size == 0 + || !enif_inspect_iolist_as_binary(env, argv[3], &aad) + || !enif_inspect_iolist_as_binary(env, argv[4], &in) + || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) { + return enif_make_badarg(env); + } + + if (!(ctx = CRYPTO_gcm128_new(&aes_key, (block128_f)AES_encrypt))) + return atom_error; + + CRYPTO_gcm128_setiv(ctx, iv.data, iv.size); + + if (CRYPTO_gcm128_aad(ctx, aad.data, aad.size)) + goto out_err; + + outp = enif_make_new_binary(env, in.size, &out); + + /* decrypt */ + if (CRYPTO_gcm128_decrypt(ctx, in.data, outp, in.size)) + goto out_err; + + /* calculate and check the tag */ + if (CRYPTO_gcm128_finish(ctx, tag.data, tag.size)) + goto out_err; + + CRYPTO_gcm128_release(ctx); + CONSUME_REDS(env, in); + + return out; + +out_err: + CRYPTO_gcm128_release(ctx); + return atom_error; +} +#endif /* HAVE_GCM_EVP_DECRYPT_BUG */ + diff --git a/lib/crypto/c_src/aes.h b/lib/crypto/c_src/aes.h new file mode 100644 index 0000000000..09c984f84a --- /dev/null +++ b/lib/crypto/c_src/aes.h @@ -0,0 +1,36 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_AES_H__ +#define E_AES_H__ 1 + +#include "common.h" + +ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#ifdef HAVE_GCM_EVP_DECRYPT_BUG +ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +#endif + +#endif /* E_AES_H__ */ diff --git a/lib/crypto/c_src/algorithms.c b/lib/crypto/c_src/algorithms.c new file mode 100644 index 0000000000..a6e61cc9b2 --- /dev/null +++ b/lib/crypto/c_src/algorithms.c @@ -0,0 +1,320 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "algorithms.h" + +static int algo_hash_cnt, algo_hash_fips_cnt; +static ERL_NIF_TERM algo_hash[12]; /* increase when extending the list */ +static int algo_pubkey_cnt, algo_pubkey_fips_cnt; +static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */ +static int algo_cipher_cnt, algo_cipher_fips_cnt; +static ERL_NIF_TERM algo_cipher[25]; /* increase when extending the list */ +static int algo_mac_cnt, algo_mac_fips_cnt; +static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */ +static int algo_curve_cnt, algo_curve_fips_cnt; +static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */ +static int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt; +static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */ + +void init_algorithms_types(ErlNifEnv* env) +{ + // Validated algorithms first + algo_hash_cnt = 0; + algo_hash[algo_hash_cnt++] = atom_sha; +#ifdef HAVE_SHA224 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha224"); +#endif +#ifdef HAVE_SHA256 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha256"); +#endif +#ifdef HAVE_SHA384 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha384"); +#endif +#ifdef HAVE_SHA512 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha512"); +#endif +#ifdef HAVE_SHA3_224 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_224"); +#endif +#ifdef HAVE_SHA3_256 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_256"); +#endif +#ifdef HAVE_SHA3_384 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_384"); +#endif +#ifdef HAVE_SHA3_512 + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512"); +#endif + // Non-validated algorithms follow + algo_hash_fips_cnt = algo_hash_cnt; + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md4"); + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md5"); + algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160"); + + algo_pubkey_cnt = 0; + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa"); + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dss"); + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dh"); +#if defined(HAVE_EC) +#if !defined(OPENSSL_NO_EC2M) + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ec_gf2m"); +#endif + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdsa"); + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdh"); +#endif + // Non-validated algorithms follow + algo_pubkey_fips_cnt = algo_pubkey_cnt; + // Don't know if Edward curves are fips validated +#if defined(HAVE_EDDSA) + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa"); +#endif + algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp"); + + // Validated algorithms first + 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"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb8"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb128"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc256"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ctr"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ecb"); +#if defined(HAVE_GCM) + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_gcm"); +#endif +#if defined(HAVE_CCM) + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ccm"); +#endif + // Non-validated algorithms follow + algo_cipher_fips_cnt = algo_cipher_cnt; +#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"); + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ecb"); +#ifndef OPENSSL_NO_RC2 + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc2_cbc"); +#endif +#ifndef OPENSSL_NO_RC4 + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc4"); +#endif +#if defined(HAVE_CHACHA20_POLY1305) + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20_poly1305"); +#endif +#if defined(HAVE_CHACHA20) + algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20"); +#endif + + // Validated algorithms first + algo_mac_cnt = 0; + algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac"); +#ifdef HAVE_CMAC + algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac"); +#endif +#ifdef HAVE_POLY1305 + algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305"); +#endif + // Non-validated algorithms follow + algo_mac_fips_cnt = algo_mac_cnt; + + // Validated algorithms first + algo_curve_cnt = 0; +#if defined(HAVE_EC) + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1"); +#if !defined(OPENSSL_NO_EC2M) + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11"); +#endif +#endif + // Non-validated algorithms follow + algo_curve_fips_cnt = algo_curve_cnt; +#if defined(HAVE_EC) + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8"); +#if !defined(OPENSSL_NO_EC2M) + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4"); +#endif +#endif + //-- +#ifdef HAVE_EDDSA + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448"); +#endif +#ifdef HAVE_ED_CURVE_DH + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519"); + algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448"); +#endif + + // Validated algorithms first + algo_rsa_opts_cnt = 0; +#ifdef HAS_EVP_PKEY_CTX +# ifdef HAVE_RSA_PKCS1_PSS_PADDING + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding"); + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen"); +# endif +# ifdef HAVE_RSA_MGF1_MD + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md"); +# endif +# ifdef HAVE_RSA_OAEP_PADDING + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding"); +# endif +# ifdef HAVE_RSA_OAEP_MD + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label"); + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md"); +# endif + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"signature_md"); +#endif + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_padding"); + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_x931_padding"); +#ifdef HAVE_RSA_SSLV23_PADDING + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_sslv23_padding"); +#endif + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding"); + algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt; + + + // Check that the max number of algos is updated + ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM)); + ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM)); + ASSERT(algo_cipher_cnt <= sizeof(algo_cipher)/sizeof(ERL_NIF_TERM)); + ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM)); + ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM)); + ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM)); +} + +ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ +#ifdef FIPS_SUPPORT + int fips_mode = FIPS_mode(); + int hash_cnt = fips_mode ? algo_hash_fips_cnt : algo_hash_cnt; + int pubkey_cnt = fips_mode ? algo_pubkey_fips_cnt : algo_pubkey_cnt; + int cipher_cnt = fips_mode ? algo_cipher_fips_cnt : algo_cipher_cnt; + int mac_cnt = fips_mode ? algo_mac_fips_cnt : algo_mac_cnt; + int curve_cnt = fips_mode ? algo_curve_fips_cnt : algo_curve_cnt; + int rsa_opts_cnt = fips_mode ? algo_rsa_opts_fips_cnt : algo_rsa_opts_cnt; +#else + int hash_cnt = algo_hash_cnt; + int pubkey_cnt = algo_pubkey_cnt; + int cipher_cnt = algo_cipher_cnt; + int mac_cnt = algo_mac_cnt; + int curve_cnt = algo_curve_cnt; + int rsa_opts_cnt = algo_rsa_opts_cnt; +#endif + return enif_make_tuple6(env, + enif_make_list_from_array(env, algo_hash, hash_cnt), + enif_make_list_from_array(env, algo_pubkey, pubkey_cnt), + enif_make_list_from_array(env, algo_cipher, cipher_cnt), + enif_make_list_from_array(env, algo_mac, mac_cnt), + enif_make_list_from_array(env, algo_curve, curve_cnt), + enif_make_list_from_array(env, algo_rsa_opts, rsa_opts_cnt) + ); +} diff --git a/lib/crypto/c_src/algorithms.h b/lib/crypto/c_src/algorithms.h new file mode 100644 index 0000000000..068fb661ec --- /dev/null +++ b/lib/crypto/c_src/algorithms.h @@ -0,0 +1,30 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_ALGORITHMS_H__ +#define E_ALGORITHMS_H__ 1 + +#include "common.h" + +void init_algorithms_types(ErlNifEnv* env); + +ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_ALGORITHMS_H__ */ diff --git a/lib/crypto/c_src/atoms.c b/lib/crypto/c_src/atoms.c new file mode 100644 index 0000000000..3a028b9a67 --- /dev/null +++ b/lib/crypto/c_src/atoms.c @@ -0,0 +1,271 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "atoms.h" + +ERL_NIF_TERM atom_true; +ERL_NIF_TERM atom_false; +ERL_NIF_TERM atom_sha; +ERL_NIF_TERM atom_error; +ERL_NIF_TERM atom_rsa_pkcs1_padding; +ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding; +ERL_NIF_TERM atom_rsa_no_padding; +ERL_NIF_TERM atom_signature_md; +ERL_NIF_TERM atom_undefined; + +ERL_NIF_TERM atom_ok; +ERL_NIF_TERM atom_not_prime; +ERL_NIF_TERM atom_not_strong_prime; +ERL_NIF_TERM atom_unable_to_check_generator; +ERL_NIF_TERM atom_not_suitable_generator; +ERL_NIF_TERM atom_check_failed; +ERL_NIF_TERM atom_unknown; +ERL_NIF_TERM atom_none; +ERL_NIF_TERM atom_notsup; +ERL_NIF_TERM atom_digest; +#ifdef FIPS_SUPPORT +ERL_NIF_TERM atom_enabled; +ERL_NIF_TERM atom_not_enabled; +#else +ERL_NIF_TERM atom_not_supported; +#endif + +#if defined(HAVE_EC) +ERL_NIF_TERM atom_ec; +ERL_NIF_TERM atom_prime_field; +ERL_NIF_TERM atom_characteristic_two_field; +ERL_NIF_TERM atom_tpbasis; +ERL_NIF_TERM atom_ppbasis; +ERL_NIF_TERM atom_onbasis; +#endif + +ERL_NIF_TERM atom_aes_cfb8; +ERL_NIF_TERM atom_aes_cfb128; +#ifdef HAVE_GCM +ERL_NIF_TERM atom_aes_gcm; +#endif +#ifdef HAVE_CCM +ERL_NIF_TERM atom_aes_ccm; +#endif +#ifdef HAVE_CHACHA20_POLY1305 +ERL_NIF_TERM atom_chacha20_poly1305; +#endif +#ifdef HAVE_ECB_IVEC_BUG +ERL_NIF_TERM atom_aes_ecb; +ERL_NIF_TERM atom_des_ecb; +ERL_NIF_TERM atom_blowfish_ecb; +#endif + +ERL_NIF_TERM atom_rsa; +ERL_NIF_TERM atom_dss; +ERL_NIF_TERM atom_ecdsa; + +#ifdef HAVE_ED_CURVE_DH +ERL_NIF_TERM atom_x25519; +ERL_NIF_TERM atom_x448; +#endif + +ERL_NIF_TERM atom_eddsa; +#ifdef HAVE_EDDSA +ERL_NIF_TERM atom_ed25519; +ERL_NIF_TERM atom_ed448; +#endif + +ERL_NIF_TERM atom_rsa_mgf1_md; +ERL_NIF_TERM atom_rsa_oaep_label; +ERL_NIF_TERM atom_rsa_oaep_md; +ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */ +ERL_NIF_TERM atom_rsa_padding; +ERL_NIF_TERM atom_rsa_pkcs1_pss_padding; +#ifdef HAVE_RSA_SSLV23_PADDING +ERL_NIF_TERM atom_rsa_sslv23_padding; +#endif +ERL_NIF_TERM atom_rsa_x931_padding; +ERL_NIF_TERM atom_rsa_pss_saltlen; +ERL_NIF_TERM atom_sha224; +ERL_NIF_TERM atom_sha256; +ERL_NIF_TERM atom_sha384; +ERL_NIF_TERM atom_sha512; +ERL_NIF_TERM atom_sha3_224; +ERL_NIF_TERM atom_sha3_256; +ERL_NIF_TERM atom_sha3_384; +ERL_NIF_TERM atom_sha3_512; +ERL_NIF_TERM atom_md5; +ERL_NIF_TERM atom_ripemd160; + +#ifdef HAS_ENGINE_SUPPORT +ERL_NIF_TERM atom_bad_engine_method; +ERL_NIF_TERM atom_bad_engine_id; +ERL_NIF_TERM atom_ctrl_cmd_failed; +ERL_NIF_TERM atom_engine_init_failed; +ERL_NIF_TERM atom_register_engine_failed; +ERL_NIF_TERM atom_add_engine_failed; +ERL_NIF_TERM atom_remove_engine_failed; +ERL_NIF_TERM atom_engine_method_not_supported; + +ERL_NIF_TERM atom_engine_method_rsa; +ERL_NIF_TERM atom_engine_method_dsa; +ERL_NIF_TERM atom_engine_method_dh; +ERL_NIF_TERM atom_engine_method_rand; +ERL_NIF_TERM atom_engine_method_ecdh; +ERL_NIF_TERM atom_engine_method_ecdsa; +ERL_NIF_TERM atom_engine_method_ciphers; +ERL_NIF_TERM atom_engine_method_digests; +ERL_NIF_TERM atom_engine_method_store; +ERL_NIF_TERM atom_engine_method_pkey_meths; +ERL_NIF_TERM atom_engine_method_pkey_asn1_meths; +ERL_NIF_TERM atom_engine_method_ec; + +ERL_NIF_TERM atom_engine; +ERL_NIF_TERM atom_key_id; +ERL_NIF_TERM atom_password; +#endif + +int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM load_info) { + atom_true = enif_make_atom(env,"true"); + atom_false = enif_make_atom(env,"false"); + /* Enter FIPS mode */ + if (fips_mode == atom_true) { +#ifdef FIPS_SUPPORT + if (!FIPS_mode_set(1)) { +#else + { +#endif + PRINTF_ERR0("CRYPTO: Could not setup FIPS mode"); + return 0; + } + } else if (fips_mode != atom_false) { + PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info); + return 0; + } + + atom_sha = enif_make_atom(env,"sha"); + atom_error = enif_make_atom(env,"error"); + atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding"); + atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding"); + atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding"); + atom_signature_md = enif_make_atom(env,"signature_md"); + atom_undefined = enif_make_atom(env,"undefined"); + atom_ok = enif_make_atom(env,"ok"); + atom_not_prime = enif_make_atom(env,"not_prime"); + atom_not_strong_prime = enif_make_atom(env,"not_strong_prime"); + atom_unable_to_check_generator = enif_make_atom(env,"unable_to_check_generator"); + atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator"); + atom_check_failed = enif_make_atom(env,"check_failed"); + atom_unknown = enif_make_atom(env,"unknown"); + atom_none = enif_make_atom(env,"none"); + atom_notsup = enif_make_atom(env,"notsup"); + atom_digest = enif_make_atom(env,"digest"); + +#if defined(HAVE_EC) + atom_ec = enif_make_atom(env,"ec"); + atom_prime_field = enif_make_atom(env,"prime_field"); + atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field"); + atom_tpbasis = enif_make_atom(env,"tpbasis"); + atom_ppbasis = enif_make_atom(env,"ppbasis"); + atom_onbasis = enif_make_atom(env,"onbasis"); +#endif + + atom_aes_cfb8 = enif_make_atom(env, "aes_cfb8"); + atom_aes_cfb128 = enif_make_atom(env, "aes_cfb128"); +#ifdef HAVE_GCM + atom_aes_gcm = enif_make_atom(env, "aes_gcm"); +#endif +#ifdef HAVE_CCM + atom_aes_ccm = enif_make_atom(env, "aes_ccm"); +#endif +#ifdef HAVE_CHACHA20_POLY1305 + atom_chacha20_poly1305 = enif_make_atom(env,"chacha20_poly1305"); +#endif +#ifdef HAVE_ECB_IVEC_BUG + atom_aes_ecb = enif_make_atom(env, "aes_ecb"); + atom_des_ecb = enif_make_atom(env, "des_ecb"); + atom_blowfish_ecb = enif_make_atom(env, "blowfish_ecb"); +#endif + +#ifdef FIPS_SUPPORT + atom_enabled = enif_make_atom(env,"enabled"); + atom_not_enabled = enif_make_atom(env,"not_enabled"); +#else + atom_not_supported = enif_make_atom(env,"not_supported"); +#endif + atom_rsa = enif_make_atom(env,"rsa"); + atom_dss = enif_make_atom(env,"dss"); + atom_ecdsa = enif_make_atom(env,"ecdsa"); +#ifdef HAVE_ED_CURVE_DH + atom_x25519 = enif_make_atom(env,"x25519"); + atom_x448 = enif_make_atom(env,"x448"); +#endif + atom_eddsa = enif_make_atom(env,"eddsa"); +#ifdef HAVE_EDDSA + atom_ed25519 = enif_make_atom(env,"ed25519"); + atom_ed448 = enif_make_atom(env,"ed448"); +#endif + atom_rsa_mgf1_md = enif_make_atom(env,"rsa_mgf1_md"); + atom_rsa_oaep_label = enif_make_atom(env,"rsa_oaep_label"); + atom_rsa_oaep_md = enif_make_atom(env,"rsa_oaep_md"); + atom_rsa_pad = enif_make_atom(env,"rsa_pad"); /* backwards compatibility */ + atom_rsa_padding = enif_make_atom(env,"rsa_padding"); + atom_rsa_pkcs1_pss_padding = enif_make_atom(env,"rsa_pkcs1_pss_padding"); +#ifdef HAVE_RSA_SSLV23_PADDING + atom_rsa_sslv23_padding = enif_make_atom(env,"rsa_sslv23_padding"); +#endif + atom_rsa_x931_padding = enif_make_atom(env,"rsa_x931_padding"); + atom_rsa_pss_saltlen = enif_make_atom(env,"rsa_pss_saltlen"); + atom_sha224 = enif_make_atom(env,"sha224"); + atom_sha256 = enif_make_atom(env,"sha256"); + atom_sha384 = enif_make_atom(env,"sha384"); + atom_sha512 = enif_make_atom(env,"sha512"); + atom_sha3_224 = enif_make_atom(env,"sha3_224"); + atom_sha3_256 = enif_make_atom(env,"sha3_256"); + atom_sha3_384 = enif_make_atom(env,"sha3_384"); + atom_sha3_512 = enif_make_atom(env,"sha3_512"); + atom_md5 = enif_make_atom(env,"md5"); + atom_ripemd160 = enif_make_atom(env,"ripemd160"); + +#ifdef HAS_ENGINE_SUPPORT + atom_bad_engine_method = enif_make_atom(env,"bad_engine_method"); + atom_bad_engine_id = enif_make_atom(env,"bad_engine_id"); + atom_ctrl_cmd_failed = enif_make_atom(env,"ctrl_cmd_failed"); + atom_engine_init_failed = enif_make_atom(env,"engine_init_failed"); + atom_engine_method_not_supported = enif_make_atom(env,"engine_method_not_supported"); + atom_add_engine_failed = enif_make_atom(env,"add_engine_failed"); + atom_remove_engine_failed = enif_make_atom(env,"remove_engine_failed"); + + atom_engine_method_rsa = enif_make_atom(env,"engine_method_rsa"); + atom_engine_method_dsa = enif_make_atom(env,"engine_method_dsa"); + atom_engine_method_dh = enif_make_atom(env,"engine_method_dh"); + atom_engine_method_rand = enif_make_atom(env,"engine_method_rand"); + atom_engine_method_ecdh = enif_make_atom(env,"engine_method_ecdh"); + atom_engine_method_ecdsa = enif_make_atom(env,"engine_method_ecdsa"); + atom_engine_method_store = enif_make_atom(env,"engine_method_store"); + atom_engine_method_ciphers = enif_make_atom(env,"engine_method_ciphers"); + atom_engine_method_digests = enif_make_atom(env,"engine_method_digests"); + atom_engine_method_pkey_meths = enif_make_atom(env,"engine_method_pkey_meths"); + atom_engine_method_pkey_asn1_meths = enif_make_atom(env,"engine_method_pkey_asn1_meths"); + atom_engine_method_ec = enif_make_atom(env,"engine_method_ec"); + + atom_engine = enif_make_atom(env,"engine"); + atom_key_id = enif_make_atom(env,"key_id"); + atom_password = enif_make_atom(env,"password"); +#endif + + return 1; +} diff --git a/lib/crypto/c_src/atoms.h b/lib/crypto/c_src/atoms.h new file mode 100644 index 0000000000..9ddf0131ac --- /dev/null +++ b/lib/crypto/c_src/atoms.h @@ -0,0 +1,147 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_ATOMS_H__ +#define E_ATOMS_H__ 1 + +#include <erl_nif.h> +#include "openssl_config.h" + +extern ERL_NIF_TERM atom_true; +extern ERL_NIF_TERM atom_false; +extern ERL_NIF_TERM atom_sha; +extern ERL_NIF_TERM atom_error; +extern ERL_NIF_TERM atom_rsa_pkcs1_padding; +extern ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding; +extern ERL_NIF_TERM atom_rsa_no_padding; +extern ERL_NIF_TERM atom_signature_md; +extern ERL_NIF_TERM atom_undefined; + +extern ERL_NIF_TERM atom_ok; +extern ERL_NIF_TERM atom_not_prime; +extern ERL_NIF_TERM atom_not_strong_prime; +extern ERL_NIF_TERM atom_unable_to_check_generator; +extern ERL_NIF_TERM atom_not_suitable_generator; +extern ERL_NIF_TERM atom_check_failed; +extern ERL_NIF_TERM atom_unknown; +extern ERL_NIF_TERM atom_none; +extern ERL_NIF_TERM atom_notsup; +extern ERL_NIF_TERM atom_digest; +#ifdef FIPS_SUPPORT +extern ERL_NIF_TERM atom_enabled; +extern ERL_NIF_TERM atom_not_enabled; +#else +extern ERL_NIF_TERM atom_not_supported; +#endif + +#if defined(HAVE_EC) +extern ERL_NIF_TERM atom_ec; +extern ERL_NIF_TERM atom_prime_field; +extern ERL_NIF_TERM atom_characteristic_two_field; +extern ERL_NIF_TERM atom_tpbasis; +extern ERL_NIF_TERM atom_ppbasis; +extern ERL_NIF_TERM atom_onbasis; +#endif + +extern ERL_NIF_TERM atom_aes_cfb8; +extern ERL_NIF_TERM atom_aes_cfb128; +#ifdef HAVE_GCM +extern ERL_NIF_TERM atom_aes_gcm; +#endif +#ifdef HAVE_CCM +extern ERL_NIF_TERM atom_aes_ccm; +#endif +#ifdef HAVE_CHACHA20_POLY1305 +extern ERL_NIF_TERM atom_chacha20_poly1305; +#endif +#ifdef HAVE_ECB_IVEC_BUG +extern ERL_NIF_TERM atom_aes_ecb; +extern ERL_NIF_TERM atom_des_ecb; +extern ERL_NIF_TERM atom_blowfish_ecb; +#endif + +extern ERL_NIF_TERM atom_rsa; +extern ERL_NIF_TERM atom_dss; +extern ERL_NIF_TERM atom_ecdsa; + +#ifdef HAVE_ED_CURVE_DH +extern ERL_NIF_TERM atom_x25519; +extern ERL_NIF_TERM atom_x448; +#endif + +extern ERL_NIF_TERM atom_eddsa; +#ifdef HAVE_EDDSA +extern ERL_NIF_TERM atom_ed25519; +extern ERL_NIF_TERM atom_ed448; +#endif + +extern ERL_NIF_TERM atom_rsa_mgf1_md; +extern ERL_NIF_TERM atom_rsa_oaep_label; +extern ERL_NIF_TERM atom_rsa_oaep_md; +extern ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */ +extern ERL_NIF_TERM atom_rsa_padding; +extern ERL_NIF_TERM atom_rsa_pkcs1_pss_padding; +#ifdef HAVE_RSA_SSLV23_PADDING +extern ERL_NIF_TERM atom_rsa_sslv23_padding; +#endif +extern ERL_NIF_TERM atom_rsa_x931_padding; +extern ERL_NIF_TERM atom_rsa_pss_saltlen; +extern ERL_NIF_TERM atom_sha224; +extern ERL_NIF_TERM atom_sha256; +extern ERL_NIF_TERM atom_sha384; +extern ERL_NIF_TERM atom_sha512; +extern ERL_NIF_TERM atom_sha3_224; +extern ERL_NIF_TERM atom_sha3_256; +extern ERL_NIF_TERM atom_sha3_384; +extern ERL_NIF_TERM atom_sha3_512; +extern ERL_NIF_TERM atom_md5; +extern ERL_NIF_TERM atom_ripemd160; + +#ifdef HAS_ENGINE_SUPPORT +extern ERL_NIF_TERM atom_bad_engine_method; +extern ERL_NIF_TERM atom_bad_engine_id; +extern ERL_NIF_TERM atom_ctrl_cmd_failed; +extern ERL_NIF_TERM atom_engine_init_failed; +extern ERL_NIF_TERM atom_register_engine_failed; +extern ERL_NIF_TERM atom_add_engine_failed; +extern ERL_NIF_TERM atom_remove_engine_failed; +extern ERL_NIF_TERM atom_engine_method_not_supported; + +extern ERL_NIF_TERM atom_engine_method_rsa; +extern ERL_NIF_TERM atom_engine_method_dsa; +extern ERL_NIF_TERM atom_engine_method_dh; +extern ERL_NIF_TERM atom_engine_method_rand; +extern ERL_NIF_TERM atom_engine_method_ecdh; +extern ERL_NIF_TERM atom_engine_method_ecdsa; +extern ERL_NIF_TERM atom_engine_method_ciphers; +extern ERL_NIF_TERM atom_engine_method_digests; +extern ERL_NIF_TERM atom_engine_method_store; +extern ERL_NIF_TERM atom_engine_method_pkey_meths; +extern ERL_NIF_TERM atom_engine_method_pkey_asn1_meths; +extern ERL_NIF_TERM atom_engine_method_ec; + +extern ERL_NIF_TERM atom_engine; +extern ERL_NIF_TERM atom_key_id; +extern ERL_NIF_TERM atom_password; +#endif + +int init_atoms(ErlNifEnv *env, const ERL_NIF_TERM fips_mode, const ERL_NIF_TERM load_info); + +#endif /* E_ATOMS_H__ */ diff --git a/lib/crypto/c_src/block.c b/lib/crypto/c_src/block.c new file mode 100644 index 0000000000..2ba3290e9f --- /dev/null +++ b/lib/crypto/c_src/block.c @@ -0,0 +1,105 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "block.h" +#include "aes.h" +#include "cipher.h" + +ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */ + struct cipher_type_t *cipherp = NULL; + const EVP_CIPHER *cipher; + ErlNifBinary key, ivec, text; + EVP_CIPHER_CTX* ctx; + ERL_NIF_TERM ret; + unsigned char *out; + int ivec_size, out_size = 0; + + if (!enif_inspect_iolist_as_binary(env, argv[1], &key) + || !(cipherp = get_cipher_type(argv[0], key.size)) + || !enif_inspect_iolist_as_binary(env, argv[argc - 2], &text)) { + return enif_make_badarg(env); + } + cipher = cipherp->cipher.p; + if (!cipher) { + return enif_raise_exception(env, atom_notsup); + } + + if (argv[0] == atom_aes_cfb8 + && (key.size == 24 || key.size == 32)) { + /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes? + * Fall back on low level API + */ + return aes_cfb_8_crypt(env, argc-1, argv+1); + } + else if (argv[0] == atom_aes_cfb128 + && (key.size == 24 || key.size == 32)) { + /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes? + * Fall back on low level API + */ + return aes_cfb_128_crypt_nif(env, argc-1, argv+1); + } + + ivec_size = EVP_CIPHER_iv_length(cipher); + +#ifdef HAVE_ECB_IVEC_BUG + if (argv[0] == atom_aes_ecb || argv[0] == atom_blowfish_ecb || + argv[0] == atom_des_ecb) + ivec_size = 0; /* 0.9.8l returns faulty ivec_size */ +#endif + + if (text.size % EVP_CIPHER_block_size(cipher) != 0 || + (ivec_size == 0 ? argc != 4 + : (argc != 5 || + !enif_inspect_iolist_as_binary(env, argv[2], &ivec) || + ivec.size != ivec_size))) { + return enif_make_badarg(env); + } + + out = enif_make_new_binary(env, text.size, &ret); + + ctx = EVP_CIPHER_CTX_new(); + if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, + (argv[argc - 1] == atom_true)) || + !EVP_CIPHER_CTX_set_key_length(ctx, key.size) || + !(EVP_CIPHER_type(cipher) != NID_rc2_cbc || + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, key.size * 8, NULL)) || + !EVP_CipherInit_ex(ctx, NULL, NULL, + key.data, ivec_size ? ivec.data : NULL, -1) || + !EVP_CIPHER_CTX_set_padding(ctx, 0)) { + + EVP_CIPHER_CTX_free(ctx); + return enif_raise_exception(env, atom_notsup); + } + + if (text.size > 0 && /* OpenSSL 0.9.8h asserts text.size > 0 */ + (!EVP_CipherUpdate(ctx, out, &out_size, text.data, text.size) + || (ASSERT(out_size == text.size), 0) + || !EVP_CipherFinal_ex(ctx, out + out_size, &out_size))) { + + EVP_CIPHER_CTX_free(ctx); + return enif_raise_exception(env, atom_notsup); + } + ASSERT(out_size == 0); + EVP_CIPHER_CTX_free(ctx); + CONSUME_REDS(env, text); + + return ret; +} diff --git a/lib/crypto/c_src/block.h b/lib/crypto/c_src/block.h new file mode 100644 index 0000000000..cc5e78ce12 --- /dev/null +++ b/lib/crypto/c_src/block.h @@ -0,0 +1,28 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_BLOCK_H__ +#define E_BLOCK_H__ 1 + +#include "common.h" + +ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_BLOCK_H__ */ diff --git a/lib/crypto/c_src/bn.c b/lib/crypto/c_src/bn.c new file mode 100644 index 0000000000..b576c46e1e --- /dev/null +++ b/lib/crypto/c_src/bn.c @@ -0,0 +1,121 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "bn.h" + + +int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp) +{ + ErlNifBinary bin; + int sz; + if (!enif_inspect_binary(env,term,&bin)) { + return 0; + } + ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size); + sz = bin.size - 4; + if (sz < 0 || get_int32(bin.data) != sz) { + return 0; + } + *bnp = BN_bin2bn(bin.data+4, sz, NULL); + return 1; +} + +int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp) +{ + ErlNifBinary bin; + if (!enif_inspect_binary(env,term,&bin)) { + return 0; + } + ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size); + *bnp = BN_bin2bn(bin.data, bin.size, NULL); + return 1; +} + +ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn) +{ + int bn_len; + unsigned char *bin_ptr; + ERL_NIF_TERM term; + + /* Copy the bignum into an erlang binary. */ + bn_len = BN_num_bytes(bn); + bin_ptr = enif_make_new_binary(env, bn_len, &term); + BN_bn2bin(bn, bin_ptr); + + return term; +} + +ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Base,Exponent,Modulo,bin_hdr) */ + BIGNUM *bn_base=NULL, *bn_exponent=NULL, *bn_modulo=NULL, *bn_result; + BN_CTX *bn_ctx; + unsigned char* ptr; + unsigned dlen; + unsigned bin_hdr; /* return type: 0=plain binary, 4: mpint */ + unsigned extra_byte; + ERL_NIF_TERM ret; + + if (!get_bn_from_bin(env, argv[0], &bn_base) + || !get_bn_from_bin(env, argv[1], &bn_exponent) + || !get_bn_from_bin(env, argv[2], &bn_modulo) + || !enif_get_uint(env,argv[3],&bin_hdr) || (bin_hdr & ~4)) { + + if (bn_base) BN_free(bn_base); + if (bn_exponent) BN_free(bn_exponent); + if (bn_modulo) BN_free(bn_modulo); + return enif_make_badarg(env); + } + bn_result = BN_new(); + bn_ctx = BN_CTX_new(); + BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx); + dlen = BN_num_bytes(bn_result); + extra_byte = bin_hdr && BN_is_bit_set(bn_result, dlen*8-1); + ptr = enif_make_new_binary(env, bin_hdr+extra_byte+dlen, &ret); + if (bin_hdr) { + put_int32(ptr, extra_byte+dlen); + ptr[4] = 0; /* extra zeroed byte to ensure a positive mpint */ + ptr += bin_hdr + extra_byte; + } + BN_bn2bin(bn_result, ptr); + BN_free(bn_result); + BN_CTX_free(bn_ctx); + BN_free(bn_modulo); + BN_free(bn_exponent); + BN_free(bn_base); + return ret; +} + +#ifdef HAVE_EC +ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn) +{ + unsigned dlen; + unsigned char* ptr; + ERL_NIF_TERM ret; + + if (!bn) + return atom_undefined; + + dlen = BN_num_bytes(bn); + ptr = enif_make_new_binary(env, dlen, &ret); + BN_bn2bin(bn, ptr); + ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen); + return ret; +} +#endif diff --git a/lib/crypto/c_src/bn.h b/lib/crypto/c_src/bn.h new file mode 100644 index 0000000000..332b06e79d --- /dev/null +++ b/lib/crypto/c_src/bn.h @@ -0,0 +1,36 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_BN_H__ +#define E_BN_H__ 1 + +#include "common.h" + +ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn); +ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#ifdef HAVE_EC +ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn); +#endif + +int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp); +int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp); + +#endif /* E_BN_H__ */ diff --git a/lib/crypto/c_src/chacha20.c b/lib/crypto/c_src/chacha20.c new file mode 100644 index 0000000000..8b21a0c7af --- /dev/null +++ b/lib/crypto/c_src/chacha20.c @@ -0,0 +1,83 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "chacha20.h" +#include "cipher.h" + +ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, IV) */ +#if defined(HAVE_CHACHA20) + ErlNifBinary key_bin, ivec_bin; + struct evp_cipher_ctx *ctx; + const EVP_CIPHER *cipher; + ERL_NIF_TERM ret; + + if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) + || !enif_inspect_binary(env, argv[1], &ivec_bin) + || key_bin.size != 32 + || ivec_bin.size != 16) { + return enif_make_badarg(env); + } + + cipher = EVP_chacha20(); + + ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); + ctx->ctx = EVP_CIPHER_CTX_new(); + + + EVP_CipherInit_ex(ctx->ctx, cipher, NULL, + key_bin.data, ivec_bin.data, 1); + EVP_CIPHER_CTX_set_padding(ctx->ctx, 0); + ret = enif_make_resource(env, ctx); + enif_release_resource(ctx); + return ret; +#else + return enif_raise_exception(env, atom_notsup); +#endif +}; + +ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (State, Data) */ +#if defined(HAVE_CHACHA20) + struct evp_cipher_ctx *ctx, *new_ctx; + ErlNifBinary data_bin; + ERL_NIF_TERM ret, cipher_term; + unsigned char *out; + int outl = 0; + + if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx) + || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) { + return enif_make_badarg(env); + } + new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); + new_ctx->ctx = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx); + out = enif_make_new_binary(env, data_bin.size, &cipher_term); + EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size); + ASSERT(outl == data_bin.size); + + ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term); + enif_release_resource(new_ctx); + CONSUME_REDS(env,data_bin); + return ret; +#else + return enif_raise_exception(env, atom_notsup); +#endif +}; diff --git a/lib/crypto/c_src/chacha20.h b/lib/crypto/c_src/chacha20.h new file mode 100644 index 0000000000..7e2ccae2bb --- /dev/null +++ b/lib/crypto/c_src/chacha20.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_CHACHA20_H__ +#define E_CHACHA20_H__ 1 + +#include "common.h" + +ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_CHACHA20_H__ */ diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c new file mode 100644 index 0000000000..6580cb183f --- /dev/null +++ b/lib/crypto/c_src/cipher.c @@ -0,0 +1,117 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "cipher.h" + +#ifdef OPENSSL_NO_DES +#define COND_NO_DES_PTR(Ptr) (NULL) +#else +#define COND_NO_DES_PTR(Ptr) (Ptr) +#endif + +static struct cipher_type_t cipher_types[] = +{ + {{"rc2_cbc"}, +#ifndef OPENSSL_NO_RC2 + {&EVP_rc2_cbc} +#else + {NULL} +#endif + }, + {{"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 + {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)} +#else + {NULL} +#endif + }, + {{"blowfish_cbc"}, {&EVP_bf_cbc}}, + {{"blowfish_cfb64"}, {&EVP_bf_cfb64}}, + {{"blowfish_ofb64"}, {&EVP_bf_ofb}}, + {{"blowfish_ecb"}, {&EVP_bf_ecb}}, + {{"aes_cbc"}, {&EVP_aes_128_cbc}, 16}, + {{"aes_cbc"}, {&EVP_aes_192_cbc}, 24}, + {{"aes_cbc"}, {&EVP_aes_256_cbc}, 32}, + {{"aes_cbc128"}, {&EVP_aes_128_cbc}}, + {{"aes_cbc256"}, {&EVP_aes_256_cbc}}, + {{"aes_cfb8"}, {&EVP_aes_128_cfb8}}, + {{"aes_cfb128"}, {&EVP_aes_128_cfb128}}, + {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16}, + {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24}, + {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32}, + {{NULL}} +}; + +#ifdef HAVE_EVP_AES_CTR +ErlNifResourceType* evp_cipher_ctx_rtype; + +static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) { + EVP_CIPHER_CTX_free(ctx->ctx); +} +#endif + +int init_cipher_ctx(ErlNifEnv *env) { +#ifdef HAVE_EVP_AES_CTR + evp_cipher_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_CIPHER_CTX", + (ErlNifResourceDtor*) evp_cipher_ctx_dtor, + ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, + NULL); + if (evp_cipher_ctx_rtype == NULL) { + PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_CIPHER_CTX'"); + return 0; + } +#endif + + return 1; +} + +void init_cipher_types(ErlNifEnv* env) +{ + struct cipher_type_t* p = cipher_types; + + for (p = cipher_types; p->type.str; p++) { + p->type.atom = enif_make_atom(env, p->type.str); + if (p->cipher.funcp) + p->cipher.p = p->cipher.funcp(); + } + p->type.atom = atom_false; /* end marker */ +} + +struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len) +{ + struct cipher_type_t* p = NULL; + for (p = cipher_types; p->type.atom != atom_false; p++) { + if (type == p->type.atom && (!p->key_len || key_len == p->key_len)) { + return p; + } + } + return NULL; +} diff --git a/lib/crypto/c_src/cipher.h b/lib/crypto/c_src/cipher.h new file mode 100644 index 0000000000..3fb27f0ba3 --- /dev/null +++ b/lib/crypto/c_src/cipher.h @@ -0,0 +1,50 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_CIPHER_H__ +#define E_CIPHER_H__ 1 + +#include "common.h" + +struct cipher_type_t { + union { + const char* str; /* before init */ + ERL_NIF_TERM atom; /* after init */ + }type; + union { + const EVP_CIPHER* (*funcp)(void); /* before init, NULL if notsup */ + const EVP_CIPHER* p; /* after init, NULL if notsup */ + }cipher; + const size_t key_len; /* != 0 to also match on key_len */ +}; + +#ifdef HAVE_EVP_AES_CTR +extern ErlNifResourceType* evp_cipher_ctx_rtype; +struct evp_cipher_ctx { + EVP_CIPHER_CTX* ctx; +}; +#endif + +int init_cipher_ctx(ErlNifEnv *env); + +void init_cipher_types(ErlNifEnv* env); +struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len); + +#endif /* E_CIPHER_H__ */ diff --git a/lib/crypto/c_src/cmac.c b/lib/crypto/c_src/cmac.c new file mode 100644 index 0000000000..526de11a01 --- /dev/null +++ b/lib/crypto/c_src/cmac.c @@ -0,0 +1,70 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "cmac.h" +#include "cipher.h" + +ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type, Key, Data) */ +#if defined(HAVE_CMAC) + struct cipher_type_t *cipherp = NULL; + const EVP_CIPHER *cipher; + CMAC_CTX *ctx; + ErlNifBinary key; + ErlNifBinary data; + ERL_NIF_TERM ret; + size_t ret_size; + + if (!enif_inspect_iolist_as_binary(env, argv[1], &key) + || !(cipherp = get_cipher_type(argv[0], key.size)) + || !enif_inspect_iolist_as_binary(env, argv[2], &data)) { + return enif_make_badarg(env); + } + cipher = cipherp->cipher.p; + if (!cipher) { + return enif_raise_exception(env, atom_notsup); + } + + ctx = CMAC_CTX_new(); + if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL)) { + CMAC_CTX_free(ctx); + return atom_notsup; + } + + if (!CMAC_Update(ctx, data.data, data.size) || + !CMAC_Final(ctx, + enif_make_new_binary(env, EVP_CIPHER_block_size(cipher), &ret), + &ret_size)) { + CMAC_CTX_free(ctx); + return atom_notsup; + } + ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher)); + + CMAC_CTX_free(ctx); + CONSUME_REDS(env, data); + return ret; +#else + /* The CMAC functionality was introduced in OpenSSL 1.0.1 + * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are + * no longer maintained. */ + return atom_notsup; +#endif +} + diff --git a/lib/crypto/c_src/cmac.h b/lib/crypto/c_src/cmac.h new file mode 100644 index 0000000000..14488def58 --- /dev/null +++ b/lib/crypto/c_src/cmac.h @@ -0,0 +1,28 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_CMAC_H__ +#define E_CMAC_H__ 1 + +#include "common.h" + +ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_CMAC_H__ */ diff --git a/lib/crypto/c_src/common.h b/lib/crypto/c_src/common.h new file mode 100644 index 0000000000..1259ba1f36 --- /dev/null +++ b/lib/crypto/c_src/common.h @@ -0,0 +1,36 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_COMMON_H__ +#define E_COMMON_H__ 1 + +#ifdef __WIN32__ +# include <windows.h> +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <erl_nif.h> +#include "openssl_config.h" +#include "atoms.h" + +#endif /* E_COMMON_H__ */ diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index ecb716c98c..fde3d99fa8 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -23,580 +23,41 @@ * Based on OpenSSL. */ -#ifdef __WIN32__ - #include <windows.h> -#endif - -#include <stdlib.h> -#include <stdio.h> -#include <string.h> - -#include <erl_nif.h> - -#define OPENSSL_THREAD_DEFINES -#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> -#include <openssl/aes.h> -#include <openssl/md5.h> -#include <openssl/md4.h> -#include <openssl/sha.h> -#include <openssl/ripemd.h> -#include <openssl/bn.h> -#include <openssl/objects.h> -#ifndef OPENSSL_NO_RC4 - #include <openssl/rc4.h> -#endif /* OPENSSL_NO_RC4 */ -#ifndef OPENSSL_NO_RC2 - #include <openssl/rc2.h> -#endif -#include <openssl/blowfish.h> -#include <openssl/rand.h> -#include <openssl/evp.h> -#include <openssl/hmac.h> -#include <openssl/err.h> - -/* Helper macro to construct a OPENSSL_VERSION_NUMBER. - * See openssl/opensslv.h - */ -#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \ - ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf) - -#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \ - PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1)) - - -/* LibreSSL was cloned from OpenSSL 1.0.1g and claims to be API and BPI compatible - * with 1.0.1. - * - * LibreSSL has the same names on include files and symbols as OpenSSL, but defines - * the OPENSSL_VERSION_NUMBER to be >= 2.0.0 - * - * Therefor works tests like this as intendend: - * OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) - * (The test is for example "2.4.2" >= "1.0.0" although the test - * with the cloned OpenSSL test would be "1.0.1" >= "1.0.0") - * - * But tests like this gives wrong result: - * OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) - * (The test is false since "2.4.2" < "1.1.0". It should have been - * true because the LibreSSL API version is "1.0.1") - * - */ - -#ifdef LIBRESSL_VERSION_NUMBER -/* A macro to test on in this file */ -#define HAS_LIBRESSL -#endif - -#ifdef HAS_LIBRESSL -/* LibreSSL dislikes FIPS */ -# ifdef FIPS_SUPPORT -# undef FIPS_SUPPORT -# endif - -# if LIBRESSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(2,7,0) -/* LibreSSL wants the 1.0.1 API */ -# define NEED_EVP_COMPATIBILITY_FUNCTIONS -# endif -#endif - - -#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) -# define NEED_EVP_COMPATIBILITY_FUNCTIONS -#endif - - -#ifndef HAS_LIBRESSL -# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) -# define HAS_EVP_PKEY_CTX -# endif -#endif - - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) -#include <openssl/modes.h> -#endif - -#include "crypto_callback.h" - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ - && !defined(OPENSSL_NO_SHA224) && defined(NID_sha224) \ - && !defined(OPENSSL_NO_SHA256) /* disabled like this in my sha.h (?) */ -# define HAVE_SHA224 -#endif -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ - && !defined(OPENSSL_NO_SHA256) && defined(NID_sha256) -# define HAVE_SHA256 -#endif -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ - && !defined(OPENSSL_NO_SHA384) && defined(NID_sha384)\ - && !defined(OPENSSL_NO_SHA512) /* disabled like this in my sha.h (?) */ -# define HAVE_SHA384 -#endif -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ - && !defined(OPENSSL_NO_SHA512) && defined(NID_sha512) -# define HAVE_SHA512 -#endif -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,7,'e') -# define HAVE_DES_ede3_cfb_encrypt -#endif - -// SHA3: -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -// An error in beta releases of 1.1.1 fixed in production release -# ifdef NID_sha3_224 -# define HAVE_SHA3_224 -# endif -# ifdef NID_sha3_256 -# define HAVE_SHA3_256 -# endif -#endif -# ifdef NID_sha3_384 -# define HAVE_SHA3_384 -# endif -# ifdef NID_sha3_512 -# define HAVE_SHA3_512 -# endif - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \ - && !defined(OPENSSL_NO_EC) \ - && !defined(OPENSSL_NO_ECDH) \ - && !defined(OPENSSL_NO_ECDSA) -# define HAVE_EC -#endif - -// (test for >= 1.1.1pre8) -#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \ - && !defined(HAS_LIBRESSL) \ - && defined(HAVE_EC) -# define HAVE_ED_CURVE_DH -# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)) -# define HAVE_EDDSA -# endif -#endif - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'c') -# define HAVE_AES_IGE -#endif - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1) -# define HAVE_EVP_AES_CTR -# define HAVE_AEAD -# define HAVE_GCM -# define HAVE_CCM -# define HAVE_CMAC -# if defined(RSA_PKCS1_OAEP_PADDING) -# define HAVE_RSA_OAEP_PADDING -# endif -# define HAVE_RSA_MGF1_MD -# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d') -# define HAVE_GCM_EVP_DECRYPT_BUG -# endif -#endif - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0) -# ifndef HAS_LIBRESSL -# define HAVE_CHACHA20 -# define HAVE_CHACHA20_POLY1305 -# define HAVE_RSA_OAEP_MD -# endif -#endif - -// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8 -#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7) -# ifndef HAS_LIBRESSL -# define HAVE_POLY1305 -# endif -#endif - -#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l') -# define HAVE_ECB_IVEC_BUG -#endif - -#ifndef HAS_LIBRESSL -# ifdef RSA_SSLV23_PADDING -# define HAVE_RSA_SSLV23_PADDING -# endif -#endif - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) -# ifdef RSA_PKCS1_PSS_PADDING -# define HAVE_RSA_PKCS1_PSS_PADDING -# endif -#endif - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'h') \ - && defined(HAVE_EC) -/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h - So if EC is disabled, you can't use Engine either.... -*/ -# define HAS_ENGINE_SUPPORT -#endif - - -#if defined(HAS_ENGINE_SUPPORT) -# include <openssl/engine.h> -#endif - -#if defined(HAVE_CMAC) -#include <openssl/cmac.h> -#endif - -#if defined(HAVE_EC) -#include <openssl/ec.h> -#include <openssl/ecdh.h> -#include <openssl/ecdsa.h> -#endif - -#ifdef VALGRIND - # include <valgrind/memcheck.h> - -/* libcrypto mixes supplied buffer contents into its entropy pool, - which makes valgrind complain about the use of uninitialized data. - We use this valgrind "request" to make sure that no such seemingly - undefined data is returned. -*/ - # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \ - VALGRIND_MAKE_MEM_DEFINED(ptr,size) - - # define ERL_VALGRIND_ASSERT_MEM_DEFINED(Ptr,Size) \ - do { \ - int __erl_valgrind_mem_defined = VALGRIND_CHECK_MEM_IS_DEFINED((Ptr),(Size)); \ - if (__erl_valgrind_mem_defined != 0) { \ - fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%ld) failed at %s:%d\r\n", \ - (Ptr),(long)(Size), __FILE__, __LINE__); \ - abort(); \ - } \ - } while (0) - -#else - # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) - # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size) -#endif - -#ifdef DEBUG - # define ASSERT(e) \ - ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\ - #e, __FILE__, __LINE__), abort(), 0))) -#else - # define ASSERT(e) ((void) 1) -#endif - -#ifdef __GNUC__ - # define INLINE __inline__ -#elif defined(__WIN32__) - # define INLINE __forceinline -#else - # define INLINE -#endif - - -#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \ - (((unsigned char*) (s))[1] << 16) | \ - (((unsigned char*) (s))[2] << 8) | \ - (((unsigned char*) (s))[3])) - -#define put_int32(s,i) \ -{ (s)[0] = (char)(((i) >> 24) & 0xff);\ - (s)[1] = (char)(((i) >> 16) & 0xff);\ - (s)[2] = (char)(((i) >> 8) & 0xff);\ - (s)[3] = (char)((i) & 0xff);\ -} - -/* This shall correspond to the similar macro in crypto.erl */ -/* Current value is: erlang:system_info(context_reductions) * 10 */ -#define MAX_BYTES_TO_NIF 20000 - -#define CONSUME_REDS(NifEnv, Ibin) \ -do { \ - int _cost = ((Ibin).size * 100) / MAX_BYTES_TO_NIF;\ - if (_cost) { \ - (void) enif_consume_timeslice((NifEnv), \ - (_cost > 100) ? 100 : _cost); \ - } \ - } while (0) - - -#ifdef NEED_EVP_COMPATIBILITY_FUNCTIONS -/* - * In OpenSSL 1.1.0, most structs are opaque. That means that - * the structs cannot be allocated as automatic variables on the - * C stack (because the size is unknown) and that it is necessary - * to use access functions. - * - * For backward compatibility to previous versions of OpenSSL, define - * on our versions of the new functions defined in 1.1.0 here, so that - * we don't have to sprinkle ifdefs throughout the code. - */ - -static HMAC_CTX *HMAC_CTX_new(void); -static void HMAC_CTX_free(HMAC_CTX *ctx); - -static HMAC_CTX *HMAC_CTX_new() -{ - HMAC_CTX *ctx = CRYPTO_malloc(sizeof(HMAC_CTX), __FILE__, __LINE__); - HMAC_CTX_init(ctx); - return ctx; -} - -static void HMAC_CTX_free(HMAC_CTX *ctx) -{ - HMAC_CTX_cleanup(ctx); - CRYPTO_free(ctx); -} - -#define EVP_MD_CTX_new() EVP_MD_CTX_create() -#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) - -static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb); - -static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb) -{ - return cb->arg; -} - -static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); -static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d); -static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q); -static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q); -static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); -static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp); - -static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) -{ - r->n = n; - r->e = e; - r->d = d; - return 1; -} - -static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) -{ - *n = r->n; - *e = r->e; - *d = r->d; -} - -static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) -{ - r->p = p; - r->q = q; - return 1; -} - -static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) -{ - *p = r->p; - *q = r->q; -} - -static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) -{ - r->dmp1 = dmp1; - r->dmq1 = dmq1; - r->iqmp = iqmp; - return 1; -} - -static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) -{ - *dmp1 = r->dmp1; - *dmq1 = r->dmq1; - *iqmp = r->iqmp; -} - -static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key); -static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g); -static INLINE void DSA_get0_pqg(const DSA *dsa, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); -static INLINE void DSA_get0_key(const DSA *dsa, - const BIGNUM **pub_key, const BIGNUM **priv_key); - -static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) -{ - d->pub_key = pub_key; - d->priv_key = priv_key; - return 1; -} - -static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - d->p = p; - d->q = q; - d->g = g; - return 1; -} - -static INLINE void -DSA_get0_pqg(const DSA *dsa, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - *p = dsa->p; - *q = dsa->q; - *g = dsa->g; -} - -static INLINE void -DSA_get0_key(const DSA *dsa, const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key) *pub_key = dsa->pub_key; - if (priv_key) *priv_key = dsa->priv_key; -} - - - -static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key); -static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); -static INLINE int DH_set_length(DH *dh, long length); -static INLINE void DH_get0_pqg(const DH *dh, - const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); -static INLINE void DH_get0_key(const DH *dh, - const BIGNUM **pub_key, const BIGNUM **priv_key); - -static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) -{ - dh->pub_key = pub_key; - dh->priv_key = priv_key; - return 1; -} - -static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) -{ - dh->p = p; - dh->q = q; - dh->g = g; - return 1; -} - -static INLINE int DH_set_length(DH *dh, long length) -{ - dh->length = length; - return 1; -} - - - -static INLINE void -DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) -{ - *p = dh->p; - *q = dh->q; - *g = dh->g; -} - -static INLINE void -DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) -{ - if (pub_key) *pub_key = dh->pub_key; - if (priv_key) *priv_key = dh->priv_key; -} - -#else /* End of compatibility definitions. */ - -#define HAVE_OPAQUE_BN_GENCB - -#endif +#include "common.h" + +#include "aead.h" +#include "aes.h" +#include "algorithms.h" +#include "block.h" +#include "bn.h" +#include "chacha20.h" +#include "cipher.h" +#include "cmac.h" +#include "dh.h" +#include "digest.h" +#include "dss.h" +#include "ec.h" +#include "ecdh.h" +#include "eddsa.h" +#include "engine.h" +#include "evp.h" +#include "fips.h" +#include "hash.h" +#include "hmac.h" +#include "info.h" +#include "math.h" +#include "pkey.h" +#include "poly1305.h" +#include "rand.h" +#include "rc4.h" +#include "rsa.h" +#include "srp.h" /* NIF interface declarations */ static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info); static void unload(ErlNifEnv* env, void* priv_data); -/* The NIFs: */ -static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -static ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -static ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -static ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -#ifdef HAVE_GCM_EVP_DECRYPT_BUG -static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -#endif - -static ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -static ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); - -/* helpers */ -static void init_algorithms_types(ErlNifEnv*); -static void init_digest_types(ErlNifEnv* env); -static void init_cipher_types(ErlNifEnv* env); -#ifdef HAVE_EC -static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg); -static int term2point(ErlNifEnv* env, ERL_NIF_TERM term, - EC_GROUP *group, EC_POINT **pptr); -#endif -static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn); - -#ifdef HAS_ENGINE_SUPPORT -static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i); -static int zero_terminate(ErlNifBinary bin, char **buf); -#endif - static int library_refc = 0; /* number of users of this dynamic library */ static int library_initialized = 0; @@ -676,329 +137,6 @@ static ErlNifFunc nif_funcs[] = { ERL_NIF_INIT(crypto,nif_funcs,load,NULL,upgrade,unload) -#define MD5_CTX_LEN (sizeof(MD5_CTX)) -#define MD4_CTX_LEN (sizeof(MD4_CTX)) -#define RIPEMD160_CTX_LEN (sizeof(RIPEMD160_CTX)) - - -static ERL_NIF_TERM atom_true; -static ERL_NIF_TERM atom_false; -static ERL_NIF_TERM atom_sha; -static ERL_NIF_TERM atom_error; -static ERL_NIF_TERM atom_rsa_pkcs1_padding; -static ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding; -static ERL_NIF_TERM atom_rsa_no_padding; -static ERL_NIF_TERM atom_signature_md; -static ERL_NIF_TERM atom_undefined; - -static ERL_NIF_TERM atom_ok; -static ERL_NIF_TERM atom_not_prime; -static ERL_NIF_TERM atom_not_strong_prime; -static ERL_NIF_TERM atom_unable_to_check_generator; -static ERL_NIF_TERM atom_not_suitable_generator; -static ERL_NIF_TERM atom_check_failed; -static ERL_NIF_TERM atom_unknown; -static ERL_NIF_TERM atom_none; -static ERL_NIF_TERM atom_notsup; -static ERL_NIF_TERM atom_digest; -#ifdef FIPS_SUPPORT -static ERL_NIF_TERM atom_enabled; -static ERL_NIF_TERM atom_not_enabled; -#else -static ERL_NIF_TERM atom_not_supported; -#endif - -#if defined(HAVE_EC) -static ERL_NIF_TERM atom_ec; -static ERL_NIF_TERM atom_prime_field; -static ERL_NIF_TERM atom_characteristic_two_field; -static ERL_NIF_TERM atom_tpbasis; -static ERL_NIF_TERM atom_ppbasis; -static ERL_NIF_TERM atom_onbasis; -#endif - -static ERL_NIF_TERM atom_aes_cfb8; -static ERL_NIF_TERM atom_aes_cfb128; -#ifdef HAVE_GCM -static ERL_NIF_TERM atom_aes_gcm; -#endif -#ifdef HAVE_CCM -static ERL_NIF_TERM atom_aes_ccm; -#endif -#ifdef HAVE_CHACHA20_POLY1305 -static ERL_NIF_TERM atom_chacha20_poly1305; -#endif -#ifdef HAVE_ECB_IVEC_BUG -static ERL_NIF_TERM atom_aes_ecb; -static ERL_NIF_TERM atom_des_ecb; -static ERL_NIF_TERM atom_blowfish_ecb; -#endif - -static ERL_NIF_TERM atom_rsa; -static ERL_NIF_TERM atom_dss; -static ERL_NIF_TERM atom_ecdsa; - -#ifdef HAVE_ED_CURVE_DH -static ERL_NIF_TERM atom_x25519; -static ERL_NIF_TERM atom_x448; -#endif - -static ERL_NIF_TERM atom_eddsa; -#ifdef HAVE_EDDSA -static ERL_NIF_TERM atom_ed25519; -static ERL_NIF_TERM atom_ed448; -#endif - -static ERL_NIF_TERM atom_rsa_mgf1_md; -static ERL_NIF_TERM atom_rsa_oaep_label; -static ERL_NIF_TERM atom_rsa_oaep_md; -static ERL_NIF_TERM atom_rsa_pad; /* backwards compatibility */ -static ERL_NIF_TERM atom_rsa_padding; -static ERL_NIF_TERM atom_rsa_pkcs1_pss_padding; -#ifdef HAVE_RSA_SSLV23_PADDING -static ERL_NIF_TERM atom_rsa_sslv23_padding; -#endif -static ERL_NIF_TERM atom_rsa_x931_padding; -static ERL_NIF_TERM atom_rsa_pss_saltlen; -static ERL_NIF_TERM atom_sha224; -static ERL_NIF_TERM atom_sha256; -static ERL_NIF_TERM atom_sha384; -static ERL_NIF_TERM atom_sha512; -static ERL_NIF_TERM atom_sha3_224; -static ERL_NIF_TERM atom_sha3_256; -static ERL_NIF_TERM atom_sha3_384; -static ERL_NIF_TERM atom_sha3_512; -static ERL_NIF_TERM atom_md5; -static ERL_NIF_TERM atom_ripemd160; - -#ifdef HAS_ENGINE_SUPPORT -static ERL_NIF_TERM atom_bad_engine_method; -static ERL_NIF_TERM atom_bad_engine_id; -static ERL_NIF_TERM atom_ctrl_cmd_failed; -static ERL_NIF_TERM atom_engine_init_failed; -static ERL_NIF_TERM atom_register_engine_failed; -static ERL_NIF_TERM atom_add_engine_failed; -static ERL_NIF_TERM atom_remove_engine_failed; -static ERL_NIF_TERM atom_engine_method_not_supported; - -static ERL_NIF_TERM atom_engine_method_rsa; -static ERL_NIF_TERM atom_engine_method_dsa; -static ERL_NIF_TERM atom_engine_method_dh; -static ERL_NIF_TERM atom_engine_method_rand; -static ERL_NIF_TERM atom_engine_method_ecdh; -static ERL_NIF_TERM atom_engine_method_ecdsa; -static ERL_NIF_TERM atom_engine_method_ciphers; -static ERL_NIF_TERM atom_engine_method_digests; -static ERL_NIF_TERM atom_engine_method_store; -static ERL_NIF_TERM atom_engine_method_pkey_meths; -static ERL_NIF_TERM atom_engine_method_pkey_asn1_meths; -static ERL_NIF_TERM atom_engine_method_ec; - -static ERL_NIF_TERM atom_engine; -static ERL_NIF_TERM atom_key_id; -static ERL_NIF_TERM atom_password; -#endif - -static ErlNifResourceType* hmac_context_rtype; -struct hmac_context -{ - ErlNifMutex* mtx; - int alive; - HMAC_CTX* ctx; -}; -static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*); - -struct digest_type_t { - union { - const char* str; /* before init, NULL for end-of-table */ - ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */ - }type; - union { - const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */ - const EVP_MD* p; /* after init, NULL if notsup */ - }md; -}; - -static struct digest_type_t digest_types[] = -{ - {{"md4"}, {&EVP_md4}}, - {{"md5"}, {&EVP_md5}}, - {{"ripemd160"}, {&EVP_ripemd160}}, - {{"sha"}, {&EVP_sha1}}, - {{"sha224"}, -#ifdef HAVE_SHA224 - {&EVP_sha224} -#else - {NULL} -#endif - }, - {{"sha256"}, -#ifdef HAVE_SHA256 - {&EVP_sha256} -#else - {NULL} -#endif - }, - {{"sha384"}, -#ifdef HAVE_SHA384 - {&EVP_sha384} -#else - {NULL} -#endif - }, - {{"sha512"}, -#ifdef HAVE_SHA512 - {&EVP_sha512} -#else - {NULL} -#endif - }, - {{"sha3_224"}, -#ifdef HAVE_SHA3_224 - {&EVP_sha3_224} -#else - {NULL} -#endif - }, - {{"sha3_256"}, -#ifdef HAVE_SHA3_256 - {&EVP_sha3_256} -#else - {NULL} -#endif - }, - {{"sha3_384"}, -#ifdef HAVE_SHA3_384 - {&EVP_sha3_384} -#else - {NULL} -#endif - }, - {{"sha3_512"}, -#ifdef HAVE_SHA3_512 - {&EVP_sha3_512} -#else - {NULL} -#endif - }, - - {{NULL}} -}; - -static struct digest_type_t* get_digest_type(ERL_NIF_TERM type); - -struct cipher_type_t { - union { - const char* str; /* before init */ - ERL_NIF_TERM atom; /* after init */ - }type; - union { - const EVP_CIPHER* (*funcp)(void); /* before init, NULL if notsup */ - const EVP_CIPHER* p; /* after init, NULL if notsup */ - }cipher; - 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 - -static struct cipher_type_t cipher_types[] = -{ - {{"rc2_cbc"}, -#ifndef OPENSSL_NO_RC2 - {&EVP_rc2_cbc} -#else - {NULL} -#endif - }, - {{"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 - {COND_NO_DES_PTR(&EVP_des_ede3_cfb8)} -#else - {NULL} -#endif - }, - {{"blowfish_cbc"}, {&EVP_bf_cbc}}, - {{"blowfish_cfb64"}, {&EVP_bf_cfb64}}, - {{"blowfish_ofb64"}, {&EVP_bf_ofb}}, - {{"blowfish_ecb"}, {&EVP_bf_ecb}}, - {{"aes_cbc"}, {&EVP_aes_128_cbc}, 16}, - {{"aes_cbc"}, {&EVP_aes_192_cbc}, 24}, - {{"aes_cbc"}, {&EVP_aes_256_cbc}, 32}, - {{"aes_cbc128"}, {&EVP_aes_128_cbc}}, - {{"aes_cbc256"}, {&EVP_aes_256_cbc}}, - {{"aes_cfb8"}, {&EVP_aes_128_cfb8}}, - {{"aes_cfb128"}, {&EVP_aes_128_cfb128}}, - {{"aes_ecb"}, {&EVP_aes_128_ecb}, 16}, - {{"aes_ecb"}, {&EVP_aes_192_ecb}, 24}, - {{"aes_ecb"}, {&EVP_aes_256_ecb}, 32}, - {{NULL}} -}; - -static struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len); - - -/* -#define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n") -#define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1) -#define PRINTF_ERR2(FMT, A1, A2) enif_fprintf(stderr, FMT "\n", A1, A2) -*/ - -#define PRINTF_ERR0(FMT) -#define PRINTF_ERR1(FMT,A1) -#define PRINTF_ERR2(FMT,A1,A2) - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) -/* Define resource types for OpenSSL context structures. */ -static ErlNifResourceType* evp_md_ctx_rtype; -struct evp_md_ctx { - EVP_MD_CTX* ctx; -}; -static void evp_md_ctx_dtor(ErlNifEnv* env, struct evp_md_ctx *ctx) { - EVP_MD_CTX_free(ctx->ctx); -} -#endif - -#ifdef HAVE_EVP_AES_CTR -static ErlNifResourceType* evp_cipher_ctx_rtype; -struct evp_cipher_ctx { - EVP_CIPHER_CTX* ctx; -}; -static void evp_cipher_ctx_dtor(ErlNifEnv* env, struct evp_cipher_ctx* ctx) { - EVP_CIPHER_CTX_free(ctx->ctx); -} -#endif - -// Engine -#ifdef HAS_ENGINE_SUPPORT -static ErlNifResourceType* engine_ctx_rtype; -struct engine_ctx { - ENGINE *engine; - char *id; -}; -static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) { - PRINTF_ERR0("engine_ctx_dtor"); - if(ctx->id) { - PRINTF_ERR1(" non empty ctx->id=%s", ctx->id); - enif_free(ctx->id); - } else - PRINTF_ERR0(" empty ctx->id=NULL"); -} -#endif static int verify_lib_version(void) { @@ -1015,46 +153,6 @@ static int verify_lib_version(void) return 1; } -#ifdef FIPS_SUPPORT -/* In FIPS mode non-FIPS algorithms are disabled and return badarg. */ -#define CHECK_NO_FIPS_MODE() { if (FIPS_mode()) return atom_notsup; } -#else -#define CHECK_NO_FIPS_MODE() -#endif - -#ifdef HAVE_DYNAMIC_CRYPTO_LIB - -# if defined(DEBUG) -static char crypto_callback_name[] = "crypto_callback.debug"; -# elif defined(VALGRIND) -static char crypto_callback_name[] = "crypto_callback.valgrind"; -# else -static char crypto_callback_name[] = "crypto_callback"; -# endif - -static int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile) -{ - int i; - - for (i = bin->size; i > 0; i--) { - if (bin->data[i-1] == '/') - break; - } - if (i + strlen(newfile) >= bufsz) { - PRINTF_ERR0("CRYPTO: lib name too long"); - return 0; - } - memcpy(buf, bin->data, i); - strcpy(buf+i, newfile); - return 1; -} - -static void error_handler(void* null, const char* errstr) -{ - PRINTF_ERR1("CRYPTO LOADING ERROR: '%s'", errstr); -} -#endif /* HAVE_DYNAMIC_CRYPTO_LIB */ - static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) { #ifdef OPENSSL_THREADS @@ -1083,44 +181,18 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) return __LINE__; } - hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context", - (ErlNifResourceDtor*) hmac_context_dtor, - ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, - NULL); - if (!hmac_context_rtype) { - PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'"); + if (!init_hmac_ctx(env)) { return __LINE__; } -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) - evp_md_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_MD_CTX", - (ErlNifResourceDtor*) evp_md_ctx_dtor, - ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, - NULL); - if (!evp_md_ctx_rtype) { - PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_MD_CTX'"); + if (!init_hash_ctx(env)) { return __LINE__; } -#endif -#ifdef HAVE_EVP_AES_CTR - evp_cipher_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_CIPHER_CTX", - (ErlNifResourceDtor*) evp_cipher_ctx_dtor, - ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, - NULL); - if (!evp_cipher_ctx_rtype) { - PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_CIPHER_CTX'"); + if (!init_cipher_ctx(env)) { return __LINE__; } -#endif -#ifdef HAS_ENGINE_SUPPORT - engine_ctx_rtype = enif_open_resource_type(env, NULL, "ENGINE_CTX", - (ErlNifResourceDtor*) engine_ctx_dtor, - ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, - NULL); - if (!engine_ctx_rtype) { - PRINTF_ERR0("CRYPTO: Could not open resource type 'ENGINE_CTX'"); + if (!init_engine_ctx(env)) { return __LINE__; } -#endif if (library_initialized) { /* Repeated loading of this library (module upgrade). @@ -1129,135 +201,10 @@ static int initialize(ErlNifEnv* env, ERL_NIF_TERM load_info) return 0; } - atom_true = enif_make_atom(env,"true"); - atom_false = enif_make_atom(env,"false"); - /* Enter FIPS mode */ - if (tpl_array[2] == atom_true) { -#ifdef FIPS_SUPPORT - if (!FIPS_mode_set(1)) { -#else - { -#endif - PRINTF_ERR0("CRYPTO: Could not setup FIPS mode"); - return 0; - } - } else if (tpl_array[2] != atom_false) { - PRINTF_ERR1("CRYPTO: Invalid load_info '%T'", load_info); - return 0; + if (!init_atoms(env, tpl_array[2], load_info)) { + return __LINE__; } - atom_sha = enif_make_atom(env,"sha"); - atom_error = enif_make_atom(env,"error"); - atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding"); - atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding"); - atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding"); - atom_signature_md = enif_make_atom(env,"signature_md"); - atom_undefined = enif_make_atom(env,"undefined"); - atom_ok = enif_make_atom(env,"ok"); - atom_not_prime = enif_make_atom(env,"not_prime"); - atom_not_strong_prime = enif_make_atom(env,"not_strong_prime"); - atom_unable_to_check_generator = enif_make_atom(env,"unable_to_check_generator"); - atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator"); - atom_check_failed = enif_make_atom(env,"check_failed"); - atom_unknown = enif_make_atom(env,"unknown"); - atom_none = enif_make_atom(env,"none"); - atom_notsup = enif_make_atom(env,"notsup"); - atom_digest = enif_make_atom(env,"digest"); - -#if defined(HAVE_EC) - atom_ec = enif_make_atom(env,"ec"); - atom_prime_field = enif_make_atom(env,"prime_field"); - atom_characteristic_two_field = enif_make_atom(env,"characteristic_two_field"); - atom_tpbasis = enif_make_atom(env,"tpbasis"); - atom_ppbasis = enif_make_atom(env,"ppbasis"); - atom_onbasis = enif_make_atom(env,"onbasis"); -#endif - - atom_aes_cfb8 = enif_make_atom(env, "aes_cfb8"); - atom_aes_cfb128 = enif_make_atom(env, "aes_cfb128"); -#ifdef HAVE_GCM - atom_aes_gcm = enif_make_atom(env, "aes_gcm"); -#endif -#ifdef HAVE_CCM - atom_aes_ccm = enif_make_atom(env, "aes_ccm"); -#endif -#ifdef HAVE_CHACHA20_POLY1305 - atom_chacha20_poly1305 = enif_make_atom(env,"chacha20_poly1305"); -#endif -#ifdef HAVE_ECB_IVEC_BUG - atom_aes_ecb = enif_make_atom(env, "aes_ecb"); - atom_des_ecb = enif_make_atom(env, "des_ecb"); - atom_blowfish_ecb = enif_make_atom(env, "blowfish_ecb"); -#endif - -#ifdef FIPS_SUPPORT - atom_enabled = enif_make_atom(env,"enabled"); - atom_not_enabled = enif_make_atom(env,"not_enabled"); -#else - atom_not_supported = enif_make_atom(env,"not_supported"); -#endif - atom_rsa = enif_make_atom(env,"rsa"); - atom_dss = enif_make_atom(env,"dss"); - atom_ecdsa = enif_make_atom(env,"ecdsa"); -#ifdef HAVE_ED_CURVE_DH - atom_x25519 = enif_make_atom(env,"x25519"); - atom_x448 = enif_make_atom(env,"x448"); -#endif - atom_eddsa = enif_make_atom(env,"eddsa"); -#ifdef HAVE_EDDSA - atom_ed25519 = enif_make_atom(env,"ed25519"); - atom_ed448 = enif_make_atom(env,"ed448"); -#endif - atom_rsa_mgf1_md = enif_make_atom(env,"rsa_mgf1_md"); - atom_rsa_oaep_label = enif_make_atom(env,"rsa_oaep_label"); - atom_rsa_oaep_md = enif_make_atom(env,"rsa_oaep_md"); - atom_rsa_pad = enif_make_atom(env,"rsa_pad"); /* backwards compatibility */ - atom_rsa_padding = enif_make_atom(env,"rsa_padding"); - atom_rsa_pkcs1_pss_padding = enif_make_atom(env,"rsa_pkcs1_pss_padding"); -#ifdef HAVE_RSA_SSLV23_PADDING - atom_rsa_sslv23_padding = enif_make_atom(env,"rsa_sslv23_padding"); -#endif - atom_rsa_x931_padding = enif_make_atom(env,"rsa_x931_padding"); - atom_rsa_pss_saltlen = enif_make_atom(env,"rsa_pss_saltlen"); - atom_sha224 = enif_make_atom(env,"sha224"); - atom_sha256 = enif_make_atom(env,"sha256"); - atom_sha384 = enif_make_atom(env,"sha384"); - atom_sha512 = enif_make_atom(env,"sha512"); - atom_sha3_224 = enif_make_atom(env,"sha3_224"); - atom_sha3_256 = enif_make_atom(env,"sha3_256"); - atom_sha3_384 = enif_make_atom(env,"sha3_384"); - atom_sha3_512 = enif_make_atom(env,"sha3_512"); - atom_md5 = enif_make_atom(env,"md5"); - atom_ripemd160 = enif_make_atom(env,"ripemd160"); - -#ifdef HAS_ENGINE_SUPPORT - atom_bad_engine_method = enif_make_atom(env,"bad_engine_method"); - atom_bad_engine_id = enif_make_atom(env,"bad_engine_id"); - atom_ctrl_cmd_failed = enif_make_atom(env,"ctrl_cmd_failed"); - atom_engine_init_failed = enif_make_atom(env,"engine_init_failed"); - atom_engine_method_not_supported = enif_make_atom(env,"engine_method_not_supported"); - atom_add_engine_failed = enif_make_atom(env,"add_engine_failed"); - atom_remove_engine_failed = enif_make_atom(env,"remove_engine_failed"); - - atom_engine_method_rsa = enif_make_atom(env,"engine_method_rsa"); - atom_engine_method_dsa = enif_make_atom(env,"engine_method_dsa"); - atom_engine_method_dh = enif_make_atom(env,"engine_method_dh"); - atom_engine_method_rand = enif_make_atom(env,"engine_method_rand"); - atom_engine_method_ecdh = enif_make_atom(env,"engine_method_ecdh"); - atom_engine_method_ecdsa = enif_make_atom(env,"engine_method_ecdsa"); - atom_engine_method_store = enif_make_atom(env,"engine_method_store"); - atom_engine_method_ciphers = enif_make_atom(env,"engine_method_ciphers"); - atom_engine_method_digests = enif_make_atom(env,"engine_method_digests"); - atom_engine_method_pkey_meths = enif_make_atom(env,"engine_method_pkey_meths"); - atom_engine_method_pkey_asn1_meths = enif_make_atom(env,"engine_method_pkey_asn1_meths"); - atom_engine_method_ec = enif_make_atom(env,"engine_method_ec"); - - atom_engine = enif_make_atom(env,"engine"); - atom_key_id = enif_make_atom(env,"key_id"); - atom_password = enif_make_atom(env,"password"); -#endif - - #ifdef HAVE_DYNAMIC_CRYPTO_LIB { void* handle; @@ -1345,4787 +292,3 @@ static void unload(ErlNifEnv* env, void* priv_data) { --library_refc; } - -static int algo_hash_cnt, algo_hash_fips_cnt; -static ERL_NIF_TERM algo_hash[12]; /* increase when extending the list */ -static int algo_pubkey_cnt, algo_pubkey_fips_cnt; -static ERL_NIF_TERM algo_pubkey[12]; /* increase when extending the list */ -static int algo_cipher_cnt, algo_cipher_fips_cnt; -static ERL_NIF_TERM algo_cipher[25]; /* increase when extending the list */ -static int algo_mac_cnt, algo_mac_fips_cnt; -static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */ -static int algo_curve_cnt, algo_curve_fips_cnt; -static ERL_NIF_TERM algo_curve[89]; /* increase when extending the list */ -static int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt; -static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */ - -static void init_algorithms_types(ErlNifEnv* env) -{ - // Validated algorithms first - algo_hash_cnt = 0; - algo_hash[algo_hash_cnt++] = atom_sha; -#ifdef HAVE_SHA224 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha224"); -#endif -#ifdef HAVE_SHA256 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha256"); -#endif -#ifdef HAVE_SHA384 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha384"); -#endif -#ifdef HAVE_SHA512 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha512"); -#endif -#ifdef HAVE_SHA3_224 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_224"); -#endif -#ifdef HAVE_SHA3_256 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_256"); -#endif -#ifdef HAVE_SHA3_384 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_384"); -#endif -#ifdef HAVE_SHA3_512 - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "sha3_512"); -#endif - // Non-validated algorithms follow - algo_hash_fips_cnt = algo_hash_cnt; - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md4"); - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "md5"); - algo_hash[algo_hash_cnt++] = enif_make_atom(env, "ripemd160"); - - algo_pubkey_cnt = 0; - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "rsa"); - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dss"); - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "dh"); -#if defined(HAVE_EC) -#if !defined(OPENSSL_NO_EC2M) - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ec_gf2m"); -#endif - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdsa"); - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "ecdh"); -#endif - // Non-validated algorithms follow - algo_pubkey_fips_cnt = algo_pubkey_cnt; - // Don't know if Edward curves are fips validated -#if defined(HAVE_EDDSA) - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "eddsa"); -#endif - algo_pubkey[algo_pubkey_cnt++] = enif_make_atom(env, "srp"); - - // Validated algorithms first - 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"); - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb8"); - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cfb128"); - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_cbc256"); - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ctr"); - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env, "aes_ecb"); -#if defined(HAVE_GCM) - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_gcm"); -#endif -#if defined(HAVE_CCM) - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"aes_ccm"); -#endif - // Non-validated algorithms follow - algo_cipher_fips_cnt = algo_cipher_cnt; -#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"); - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"blowfish_ecb"); -#ifndef OPENSSL_NO_RC2 - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc2_cbc"); -#endif -#ifndef OPENSSL_NO_RC4 - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"rc4"); -#endif -#if defined(HAVE_CHACHA20_POLY1305) - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20_poly1305"); -#endif -#if defined(HAVE_CHACHA20) - algo_cipher[algo_cipher_cnt++] = enif_make_atom(env,"chacha20"); -#endif - - // Validated algorithms first - algo_mac_cnt = 0; - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"hmac"); -#ifdef HAVE_CMAC - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"cmac"); -#endif -#ifdef HAVE_POLY1305 - algo_mac[algo_mac_cnt++] = enif_make_atom(env,"poly1305"); -#endif - // Non-validated algorithms follow - algo_mac_fips_cnt = algo_mac_cnt; - - // Validated algorithms first - algo_curve_cnt = 0; -#if defined(HAVE_EC) - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp160r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp192k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp224r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp256r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp384r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp521r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime192v3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime239v3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"prime256v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls7"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls9"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls12"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP160t1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP192t1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP224t1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP256t1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP320t1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP384t1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"brainpoolP512t1"); -#if !defined(OPENSSL_NO_EC2M) - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect163r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect193r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect233r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect239k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect283r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect409r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571k1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect571r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb163v3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb176v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb191v3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb208w1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb239v3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb272w1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb304w1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb359v1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2pnb368w1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"c2tnb431r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls5"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls10"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls11"); -#endif -#endif - // Non-validated algorithms follow - algo_curve_fips_cnt = algo_curve_cnt; -#if defined(HAVE_EC) - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp112r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"secp128r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls6"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls8"); -#if !defined(OPENSSL_NO_EC2M) - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect113r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"sect131r2"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls1"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"wtls4"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec3"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ipsec4"); -#endif -#endif - //-- -#ifdef HAVE_EDDSA - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed25519"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"ed448"); -#endif -#ifdef HAVE_ED_CURVE_DH - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x25519"); - algo_curve[algo_curve_cnt++] = enif_make_atom(env,"x448"); -#endif - - // Validated algorithms first - algo_rsa_opts_cnt = 0; -#ifdef HAS_EVP_PKEY_CTX -# ifdef HAVE_RSA_PKCS1_PSS_PADDING - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding"); - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen"); -# endif -# ifdef HAVE_RSA_MGF1_MD - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md"); -# endif -# ifdef HAVE_RSA_OAEP_PADDING - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding"); -# endif -# ifdef HAVE_RSA_OAEP_MD - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label"); - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md"); -# endif - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"signature_md"); -#endif - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_padding"); - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_x931_padding"); -#ifdef HAVE_RSA_SSLV23_PADDING - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_sslv23_padding"); -#endif - algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_no_padding"); - algo_rsa_opts_fips_cnt = algo_rsa_opts_cnt; - - - // Check that the max number of algos is updated - ASSERT(algo_hash_cnt <= sizeof(algo_hash)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_pubkey_cnt <= sizeof(algo_pubkey)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_cipher_cnt <= sizeof(algo_cipher)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_mac_cnt <= sizeof(algo_mac)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_curve_cnt <= sizeof(algo_curve)/sizeof(ERL_NIF_TERM)); - ASSERT(algo_rsa_opts_cnt <= sizeof(algo_rsa_opts)/sizeof(ERL_NIF_TERM)); -} - -static ERL_NIF_TERM algorithms(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ -#ifdef FIPS_SUPPORT - int fips_mode = FIPS_mode(); - int hash_cnt = fips_mode ? algo_hash_fips_cnt : algo_hash_cnt; - int pubkey_cnt = fips_mode ? algo_pubkey_fips_cnt : algo_pubkey_cnt; - int cipher_cnt = fips_mode ? algo_cipher_fips_cnt : algo_cipher_cnt; - int mac_cnt = fips_mode ? algo_mac_fips_cnt : algo_mac_cnt; - int curve_cnt = fips_mode ? algo_curve_fips_cnt : algo_curve_cnt; - int rsa_opts_cnt = fips_mode ? algo_rsa_opts_fips_cnt : algo_rsa_opts_cnt; -#else - int hash_cnt = algo_hash_cnt; - int pubkey_cnt = algo_pubkey_cnt; - int cipher_cnt = algo_cipher_cnt; - int mac_cnt = algo_mac_cnt; - int curve_cnt = algo_curve_cnt; - int rsa_opts_cnt = algo_rsa_opts_cnt; -#endif - return enif_make_tuple6(env, - enif_make_list_from_array(env, algo_hash, hash_cnt), - enif_make_list_from_array(env, algo_pubkey, pubkey_cnt), - enif_make_list_from_array(env, algo_cipher, cipher_cnt), - enif_make_list_from_array(env, algo_mac, mac_cnt), - enif_make_list_from_array(env, algo_curve, curve_cnt), - enif_make_list_from_array(env, algo_rsa_opts, rsa_opts_cnt) - ); -} - -static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */ - - static const char libname[] = "OpenSSL"; - unsigned name_sz = strlen(libname); - const char* ver = SSLeay_version(SSLEAY_VERSION); - unsigned ver_sz = strlen(ver); - ERL_NIF_TERM name_term, ver_term; - int ver_num = OPENSSL_VERSION_NUMBER; - /* R16: - * Ignore library version number from SSLeay() and instead show header - * version. Otherwise user might try to call a function that is implemented - * by a newer library but not supported by the headers used at compile time. - * Example: DES_ede3_cfb_encrypt in 0.9.7i but not in 0.9.7d. - * - * Version string is still from library though. - */ - - memcpy(enif_make_new_binary(env, name_sz, &name_term), libname, name_sz); - memcpy(enif_make_new_binary(env, ver_sz, &ver_term), ver, ver_sz); - - return enif_make_list1(env, enif_make_tuple3(env, name_term, - enif_make_int(env, ver_num), - ver_term)); -} - -static ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ -#ifdef FIPS_SUPPORT - return FIPS_mode() ? atom_enabled : atom_not_enabled; -#else - return atom_not_supported; -#endif -} - -static ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Boolean) */ - if (argv[0] == atom_true) { -#ifdef FIPS_SUPPORT - if (FIPS_mode_set(1)) { - return atom_true; - } -#endif - PRINTF_ERR0("CRYPTO: Could not setup FIPS mode"); - return atom_false; - } else if (argv[0] == atom_false) { -#ifdef FIPS_SUPPORT - if (!FIPS_mode_set(0)) { - return atom_false; - } -#endif - return atom_true; - } else { - return enif_make_badarg(env); - } -} - - -#if defined(HAVE_EC) -static ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env) -{ - ERL_NIF_TERM reason; - if (enif_has_pending_exception(env, &reason)) - return reason; /* dummy return value ignored */ - else - return enif_make_badarg(env); -} -#endif - -static ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Data) */ - struct digest_type_t *digp = NULL; - const EVP_MD *md; - ErlNifBinary data; - ERL_NIF_TERM ret; - unsigned ret_size; - - digp = get_digest_type(argv[0]); - if (!digp || - !enif_inspect_iolist_as_binary(env, argv[1], &data)) { - return enif_make_badarg(env); - } - md = digp->md.p; - if (!md) { - return atom_notsup; - } - - ret_size = (unsigned)EVP_MD_size(md); - ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE); - if (!EVP_Digest(data.data, data.size, - enif_make_new_binary(env, ret_size, &ret), &ret_size, - md, NULL)) { - return atom_notsup; - } - ASSERT(ret_size == (unsigned)EVP_MD_size(md)); - - CONSUME_REDS(env, data); - return ret; -} - -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) - -static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type) */ - struct digest_type_t *digp = NULL; - struct evp_md_ctx *ctx; - ERL_NIF_TERM ret; - - digp = get_digest_type(argv[0]); - if (!digp) { - return enif_make_badarg(env); - } - if (!digp->md.p) { - return atom_notsup; - } - - ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx)); - ctx->ctx = EVP_MD_CTX_new(); - if (!EVP_DigestInit(ctx->ctx, digp->md.p)) { - enif_release_resource(ctx); - return atom_notsup; - } - ret = enif_make_resource(env, ctx); - enif_release_resource(ctx); - return ret; -} -static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Context, Data) */ - struct evp_md_ctx *ctx, *new_ctx; - ErlNifBinary data; - ERL_NIF_TERM ret; - - if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx) || - !enif_inspect_iolist_as_binary(env, argv[1], &data)) { - return enif_make_badarg(env); - } - - new_ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx)); - new_ctx->ctx = EVP_MD_CTX_new(); - if (!EVP_MD_CTX_copy(new_ctx->ctx, ctx->ctx) || - !EVP_DigestUpdate(new_ctx->ctx, data.data, data.size)) { - enif_release_resource(new_ctx); - return atom_notsup; - } - - ret = enif_make_resource(env, new_ctx); - enif_release_resource(new_ctx); - CONSUME_REDS(env, data); - return ret; -} -static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Context) */ - struct evp_md_ctx *ctx; - EVP_MD_CTX *new_ctx; - ERL_NIF_TERM ret; - unsigned ret_size; - - if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx)) { - return enif_make_badarg(env); - } - - ret_size = (unsigned)EVP_MD_CTX_size(ctx->ctx); - ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE); - - new_ctx = EVP_MD_CTX_new(); - if (!EVP_MD_CTX_copy(new_ctx, ctx->ctx) || - !EVP_DigestFinal(new_ctx, - enif_make_new_binary(env, ret_size, &ret), - &ret_size)) { - EVP_MD_CTX_free(new_ctx); - return atom_notsup; - } - EVP_MD_CTX_free(new_ctx); - ASSERT(ret_size == (unsigned)EVP_MD_CTX_size(ctx->ctx)); - - return ret; -} - -#else /* if OPENSSL_VERSION_NUMBER < 1.0 */ - -static ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type) */ - typedef int (*init_fun)(unsigned char*); - struct digest_type_t *digp = NULL; - ERL_NIF_TERM ctx; - size_t ctx_size = 0; - init_fun ctx_init = 0; - - digp = get_digest_type(argv[0]); - if (!digp) { - return enif_make_badarg(env); - } - if (!digp->md.p) { - return atom_notsup; - } - - switch (EVP_MD_type(digp->md.p)) - { - case NID_md4: - ctx_size = MD4_CTX_LEN; - ctx_init = (init_fun)(&MD4_Init); - break; - case NID_md5: - ctx_size = MD5_CTX_LEN; - ctx_init = (init_fun)(&MD5_Init); - break; - case NID_ripemd160: - ctx_size = RIPEMD160_CTX_LEN; - ctx_init = (init_fun)(&RIPEMD160_Init); - break; - case NID_sha1: - ctx_size = sizeof(SHA_CTX); - ctx_init = (init_fun)(&SHA1_Init); - break; -#ifdef HAVE_SHA224 - case NID_sha224: - ctx_size = sizeof(SHA256_CTX); - ctx_init = (init_fun)(&SHA224_Init); - break; -#endif -#ifdef HAVE_SHA256 - case NID_sha256: - ctx_size = sizeof(SHA256_CTX); - ctx_init = (init_fun)(&SHA256_Init); - break; -#endif -#ifdef HAVE_SHA384 - case NID_sha384: - ctx_size = sizeof(SHA512_CTX); - ctx_init = (init_fun)(&SHA384_Init); - break; -#endif -#ifdef HAVE_SHA512 - case NID_sha512: - ctx_size = sizeof(SHA512_CTX); - ctx_init = (init_fun)(&SHA512_Init); - break; -#endif - default: - return atom_notsup; - } - ASSERT(ctx_size); - ASSERT(ctx_init); - - ctx_init(enif_make_new_binary(env, ctx_size, &ctx)); - return enif_make_tuple2(env, argv[0], ctx); -} -static ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* ({Type, Context}, Data) */ - typedef int (*update_fun)(unsigned char*, const unsigned char*, size_t); - ERL_NIF_TERM new_ctx; - ErlNifBinary ctx, data; - const ERL_NIF_TERM *tuple; - int arity; - struct digest_type_t *digp = NULL; - unsigned char *ctx_buff; - size_t ctx_size = 0; - update_fun ctx_update = 0; - - if (!enif_get_tuple(env, argv[0], &arity, &tuple) || - arity != 2 || - !(digp = get_digest_type(tuple[0])) || - !enif_inspect_binary(env, tuple[1], &ctx) || - !enif_inspect_iolist_as_binary(env, argv[1], &data)) { - return enif_make_badarg(env); - } - if (!digp->md.p) { - return atom_notsup; - } - - switch (EVP_MD_type(digp->md.p)) - { - case NID_md4: - ctx_size = MD4_CTX_LEN; - ctx_update = (update_fun)(&MD4_Update); - break; - case NID_md5: - ctx_size = MD5_CTX_LEN; - ctx_update = (update_fun)(&MD5_Update); - break; - case NID_ripemd160: - ctx_size = RIPEMD160_CTX_LEN; - ctx_update = (update_fun)(&RIPEMD160_Update); - break; - case NID_sha1: - ctx_size = sizeof(SHA_CTX); - ctx_update = (update_fun)(&SHA1_Update); - break; -#ifdef HAVE_SHA224 - case NID_sha224: - ctx_size = sizeof(SHA256_CTX); - ctx_update = (update_fun)(&SHA224_Update); - break; -#endif -#ifdef HAVE_SHA256 - case NID_sha256: - ctx_size = sizeof(SHA256_CTX); - ctx_update = (update_fun)(&SHA256_Update); - break; -#endif -#ifdef HAVE_SHA384 - case NID_sha384: - ctx_size = sizeof(SHA512_CTX); - ctx_update = (update_fun)(&SHA384_Update); - break; -#endif -#ifdef HAVE_SHA512 - case NID_sha512: - ctx_size = sizeof(SHA512_CTX); - ctx_update = (update_fun)(&SHA512_Update); - break; -#endif - default: - return atom_notsup; - } - ASSERT(ctx_size); - ASSERT(ctx_update); - - if (ctx.size != ctx_size) { - return enif_make_badarg(env); - } - - ctx_buff = enif_make_new_binary(env, ctx_size, &new_ctx); - memcpy(ctx_buff, ctx.data, ctx_size); - ctx_update(ctx_buff, data.data, data.size); - - CONSUME_REDS(env, data); - return enif_make_tuple2(env, tuple[0], new_ctx); -} -static ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* ({Type, Context}) */ - typedef int (*final_fun)(unsigned char*, void*); - ERL_NIF_TERM ret; - ErlNifBinary ctx; - const ERL_NIF_TERM *tuple; - int arity; - struct digest_type_t *digp = NULL; - const EVP_MD *md; - void *new_ctx; - size_t ctx_size = 0; - final_fun ctx_final = 0; - - if (!enif_get_tuple(env, argv[0], &arity, &tuple) || - arity != 2 || - !(digp = get_digest_type(tuple[0])) || - !enif_inspect_binary(env, tuple[1], &ctx)) { - return enif_make_badarg(env); - } - md = digp->md.p; - if (!md) { - return atom_notsup; - } - - - switch (EVP_MD_type(md)) - { - case NID_md4: - ctx_size = MD4_CTX_LEN; - ctx_final = (final_fun)(&MD4_Final); - break; - case NID_md5: - ctx_size = MD5_CTX_LEN; - ctx_final = (final_fun)(&MD5_Final); - break; - case NID_ripemd160: - ctx_size = RIPEMD160_CTX_LEN; - ctx_final = (final_fun)(&RIPEMD160_Final); - break; - case NID_sha1: - ctx_size = sizeof(SHA_CTX); - ctx_final = (final_fun)(&SHA1_Final); - break; -#ifdef HAVE_SHA224 - case NID_sha224: - ctx_size = sizeof(SHA256_CTX); - ctx_final = (final_fun)(&SHA224_Final); - break; -#endif -#ifdef HAVE_SHA256 - case NID_sha256: - ctx_size = sizeof(SHA256_CTX); - ctx_final = (final_fun)(&SHA256_Final); - break; -#endif -#ifdef HAVE_SHA384 - case NID_sha384: - ctx_size = sizeof(SHA512_CTX); - ctx_final = (final_fun)(&SHA384_Final); - break; -#endif -#ifdef HAVE_SHA512 - case NID_sha512: - ctx_size = sizeof(SHA512_CTX); - ctx_final = (final_fun)(&SHA512_Final); - break; -#endif - default: - return atom_notsup; - } - ASSERT(ctx_size); - ASSERT(ctx_final); - - if (ctx.size != ctx_size) { - return enif_make_badarg(env); - } - - new_ctx = enif_alloc(ctx_size); - memcpy(new_ctx, ctx.data, ctx_size); - ctx_final(enif_make_new_binary(env, (size_t)EVP_MD_size(md), &ret), - new_ctx); - enif_free(new_ctx); - - return ret; -} -#endif /* OPENSSL_VERSION_NUMBER < 1.0 */ - - -static ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */ - struct digest_type_t *digp = NULL; - ErlNifBinary key, data; - unsigned char buff[EVP_MAX_MD_SIZE]; - unsigned size = 0, req_size = 0; - ERL_NIF_TERM ret; - - digp = get_digest_type(argv[0]); - if (!digp || - !enif_inspect_iolist_as_binary(env, argv[1], &key) || - !enif_inspect_iolist_as_binary(env, argv[2], &data) || - (argc == 4 && !enif_get_uint(env, argv[3], &req_size))) { - return enif_make_badarg(env); - } - - if (!digp->md.p || - !HMAC(digp->md.p, - key.data, key.size, - data.data, data.size, - buff, &size)) { - return atom_notsup; - } - ASSERT(0 < size && size <= EVP_MAX_MD_SIZE); - CONSUME_REDS(env, data); - - if (argc == 4) { - if (req_size <= size) { - size = req_size; - } - else { - return enif_make_badarg(env); - } - } - memcpy(enif_make_new_binary(env, size, &ret), buff, size); - return ret; -} - -static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj) -{ - if (obj->alive) { - HMAC_CTX_free(obj->ctx); - obj->alive = 0; - } - enif_mutex_destroy(obj->mtx); -} - -static ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key) */ - struct digest_type_t *digp = NULL; - ErlNifBinary key; - ERL_NIF_TERM ret; - struct hmac_context *obj; - - digp = get_digest_type(argv[0]); - if (!digp || - !enif_inspect_iolist_as_binary(env, argv[1], &key)) { - return enif_make_badarg(env); - } - if (!digp->md.p) { - return atom_notsup; - } - - obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context)); - obj->mtx = enif_mutex_create("crypto.hmac"); - obj->alive = 1; - obj->ctx = HMAC_CTX_new(); -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) - // Check the return value of HMAC_Init: it may fail in FIPS mode - // for disabled algorithms - if (!HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL)) { - enif_release_resource(obj); - return atom_notsup; - } -#else - HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL); -#endif - - ret = enif_make_resource(env, obj); - enif_release_resource(obj); - return ret; -} - -static ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Context, Data) */ - ErlNifBinary data; - struct hmac_context* obj; - - if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj) - || !enif_inspect_iolist_as_binary(env, argv[1], &data)) { - return enif_make_badarg(env); - } - enif_mutex_lock(obj->mtx); - if (!obj->alive) { - enif_mutex_unlock(obj->mtx); - return enif_make_badarg(env); - } - HMAC_Update(obj->ctx, data.data, data.size); - enif_mutex_unlock(obj->mtx); - - CONSUME_REDS(env,data); - return argv[0]; -} - -static ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Context) or (Context, HashLen) */ - ERL_NIF_TERM ret; - struct hmac_context* obj; - unsigned char mac_buf[EVP_MAX_MD_SIZE]; - unsigned char * mac_bin; - unsigned int req_len = 0; - unsigned int mac_len; - - if (!enif_get_resource(env,argv[0],hmac_context_rtype, (void**)&obj) - || (argc == 2 && !enif_get_uint(env, argv[1], &req_len))) { - return enif_make_badarg(env); - } - - enif_mutex_lock(obj->mtx); - if (!obj->alive) { - enif_mutex_unlock(obj->mtx); - return enif_make_badarg(env); - } - - HMAC_Final(obj->ctx, mac_buf, &mac_len); - HMAC_CTX_free(obj->ctx); - obj->alive = 0; - enif_mutex_unlock(obj->mtx); - - if (argc == 2 && req_len < mac_len) { - /* Only truncate to req_len bytes if asked. */ - mac_len = req_len; - } - mac_bin = enif_make_new_binary(env, mac_len, &ret); - memcpy(mac_bin, mac_buf, mac_len); - - return ret; -} - -static ERL_NIF_TERM cmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key, Data) */ -#if defined(HAVE_CMAC) - struct cipher_type_t *cipherp = NULL; - const EVP_CIPHER *cipher; - CMAC_CTX *ctx; - ErlNifBinary key; - ErlNifBinary data; - ERL_NIF_TERM ret; - size_t ret_size; - - if (!enif_inspect_iolist_as_binary(env, argv[1], &key) - || !(cipherp = get_cipher_type(argv[0], key.size)) - || !enif_inspect_iolist_as_binary(env, argv[2], &data)) { - return enif_make_badarg(env); - } - cipher = cipherp->cipher.p; - if (!cipher) { - return enif_raise_exception(env, atom_notsup); - } - - ctx = CMAC_CTX_new(); - if (!CMAC_Init(ctx, key.data, key.size, cipher, NULL)) { - CMAC_CTX_free(ctx); - return atom_notsup; - } - - if (!CMAC_Update(ctx, data.data, data.size) || - !CMAC_Final(ctx, - enif_make_new_binary(env, EVP_CIPHER_block_size(cipher), &ret), - &ret_size)) { - CMAC_CTX_free(ctx); - return atom_notsup; - } - ASSERT(ret_size == (unsigned)EVP_CIPHER_block_size(cipher)); - - CMAC_CTX_free(ctx); - CONSUME_REDS(env, data); - return ret; -#else - /* The CMAC functionality was introduced in OpenSSL 1.0.1 - * Although OTP requires at least version 0.9.8, the versions 0.9.8 and 1.0.0 are - * no longer maintained. */ - return atom_notsup; -#endif -} - -/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */ -static ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, Text) */ -#ifdef HAVE_POLY1305 - ErlNifBinary key_bin, text, ret_bin; - ERL_NIF_TERM ret = atom_error; - EVP_PKEY *key = NULL; - EVP_MD_CTX *mctx = NULL; - EVP_PKEY_CTX *pctx = NULL; - const EVP_MD *md = NULL; - size_t size; - int type; - - type = EVP_PKEY_POLY1305; - - if (!enif_inspect_binary(env, argv[0], &key_bin) || - !(key_bin.size == 32) ) { - return enif_make_badarg(env); - } - - if (!enif_inspect_binary(env, argv[1], &text) ) { - return enif_make_badarg(env); - } - - key = EVP_PKEY_new_raw_private_key(type, /*engine*/ NULL, key_bin.data, key_bin.size); - - if (!key || - !(mctx = EVP_MD_CTX_new()) || - !EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) || - !EVP_DigestSignUpdate(mctx, text.data, text.size)) { - goto err; - } - - if (!EVP_DigestSignFinal(mctx, NULL, &size) || - !enif_alloc_binary(size, &ret_bin) || - !EVP_DigestSignFinal(mctx, ret_bin.data, &size)) { - goto err; - } - - if ((size != ret_bin.size) && - !enif_realloc_binary(&ret_bin, size)) { - goto err; - } - - ret = enif_make_binary(env, &ret_bin); - - err: - EVP_MD_CTX_free(mctx); - EVP_PKEY_free(key); - return ret; - -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM block_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type, Key, Ivec, Text, IsEncrypt) or (Type, Key, Text, IsEncrypt) */ - struct cipher_type_t *cipherp = NULL; - const EVP_CIPHER *cipher; - ErlNifBinary key, ivec, text; - EVP_CIPHER_CTX* ctx; - ERL_NIF_TERM ret; - unsigned char *out; - int ivec_size, out_size = 0; - - if (!enif_inspect_iolist_as_binary(env, argv[1], &key) - || !(cipherp = get_cipher_type(argv[0], key.size)) - || !enif_inspect_iolist_as_binary(env, argv[argc - 2], &text)) { - return enif_make_badarg(env); - } - cipher = cipherp->cipher.p; - if (!cipher) { - return enif_raise_exception(env, atom_notsup); - } - - if (argv[0] == atom_aes_cfb8 - && (key.size == 24 || key.size == 32)) { - /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes? - * Fall back on low level API - */ - return aes_cfb_8_crypt(env, argc-1, argv+1); - } - else if (argv[0] == atom_aes_cfb128 - && (key.size == 24 || key.size == 32)) { - /* Why do EVP_CIPHER_CTX_set_key_length() fail on these key sizes? - * Fall back on low level API - */ - return aes_cfb_128_crypt_nif(env, argc-1, argv+1); - } - - ivec_size = EVP_CIPHER_iv_length(cipher); - -#ifdef HAVE_ECB_IVEC_BUG - if (argv[0] == atom_aes_ecb || argv[0] == atom_blowfish_ecb || - argv[0] == atom_des_ecb) - ivec_size = 0; /* 0.9.8l returns faulty ivec_size */ -#endif - - if (text.size % EVP_CIPHER_block_size(cipher) != 0 || - (ivec_size == 0 ? argc != 4 - : (argc != 5 || - !enif_inspect_iolist_as_binary(env, argv[2], &ivec) || - ivec.size != ivec_size))) { - return enif_make_badarg(env); - } - - out = enif_make_new_binary(env, text.size, &ret); - - ctx = EVP_CIPHER_CTX_new(); - if (!EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, - (argv[argc - 1] == atom_true)) || - !EVP_CIPHER_CTX_set_key_length(ctx, key.size) || - !(EVP_CIPHER_type(cipher) != NID_rc2_cbc || - EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_SET_RC2_KEY_BITS, key.size * 8, NULL)) || - !EVP_CipherInit_ex(ctx, NULL, NULL, - key.data, ivec_size ? ivec.data : NULL, -1) || - !EVP_CIPHER_CTX_set_padding(ctx, 0)) { - - EVP_CIPHER_CTX_free(ctx); - return enif_raise_exception(env, atom_notsup); - } - - if (text.size > 0 && /* OpenSSL 0.9.8h asserts text.size > 0 */ - (!EVP_CipherUpdate(ctx, out, &out_size, text.data, text.size) - || (ASSERT(out_size == text.size), 0) - || !EVP_CipherFinal_ex(ctx, out + out_size, &out_size))) { - - EVP_CIPHER_CTX_free(ctx); - return enif_raise_exception(env, atom_notsup); - } - ASSERT(out_size == 0); - EVP_CIPHER_CTX_free(ctx); - CONSUME_REDS(env, text); - - return ret; -} - -static ERL_NIF_TERM aes_cfb_8_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, IVec, Data, IsEncrypt) */ - ErlNifBinary key, ivec, text; - AES_KEY aes_key; - unsigned char ivec_clone[16]; /* writable copy */ - int new_ivlen = 0; - ERL_NIF_TERM ret; - - CHECK_NO_FIPS_MODE(); - - if (!enif_inspect_iolist_as_binary(env, argv[0], &key) - || !(key.size == 16 || key.size == 24 || key.size == 32) - || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16 - || !enif_inspect_iolist_as_binary(env, argv[2], &text)) { - return enif_make_badarg(env); - } - - memcpy(ivec_clone, ivec.data, 16); - AES_set_encrypt_key(key.data, key.size * 8, &aes_key); - AES_cfb8_encrypt((unsigned char *) text.data, - enif_make_new_binary(env, text.size, &ret), - text.size, &aes_key, ivec_clone, &new_ivlen, - (argv[3] == atom_true)); - CONSUME_REDS(env,text); - return ret; -} - -static ERL_NIF_TERM aes_cfb_128_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, IVec, Data, IsEncrypt) */ - ErlNifBinary key, ivec, text; - AES_KEY aes_key; - unsigned char ivec_clone[16]; /* writable copy */ - int new_ivlen = 0; - ERL_NIF_TERM ret; - - if (!enif_inspect_iolist_as_binary(env, argv[0], &key) - || !(key.size == 16 || key.size == 24 || key.size == 32) - || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16 - || !enif_inspect_iolist_as_binary(env, argv[2], &text)) { - return enif_make_badarg(env); - } - - memcpy(ivec_clone, ivec.data, 16); - AES_set_encrypt_key(key.data, key.size * 8, &aes_key); - AES_cfb128_encrypt((unsigned char *) text.data, - enif_make_new_binary(env, text.size, &ret), - text.size, &aes_key, ivec_clone, &new_ivlen, - (argv[3] == atom_true)); - CONSUME_REDS(env,text); - return ret; -} - -static ERL_NIF_TERM aes_ige_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, IVec, Data, IsEncrypt) */ -#ifdef HAVE_AES_IGE - ErlNifBinary key_bin, ivec_bin, data_bin; - AES_KEY aes_key; - unsigned char ivec[32]; - int i; - unsigned char* ret_ptr; - ERL_NIF_TERM ret; - - CHECK_NO_FIPS_MODE(); - - if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) - || (key_bin.size != 16 && key_bin.size != 32) - || !enif_inspect_binary(env, argv[1], &ivec_bin) - || ivec_bin.size != 32 - || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin) - || data_bin.size % 16 != 0) { - - return enif_make_badarg(env); - } - - if (argv[3] == atom_true) { - i = AES_ENCRYPT; - AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key); - } - else { - i = AES_DECRYPT; - AES_set_decrypt_key(key_bin.data, key_bin.size*8, &aes_key); - } - - ret_ptr = enif_make_new_binary(env, data_bin.size, &ret); - memcpy(ivec, ivec_bin.data, 32); /* writable copy */ - AES_ige_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, i); - CONSUME_REDS(env,data_bin); - return ret; -#else - return atom_notsup; -#endif -} - - -/* Initializes state for ctr streaming (de)encryption -*/ -#ifdef HAVE_EVP_AES_CTR -static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, IVec) */ - ErlNifBinary key_bin, ivec_bin; - struct evp_cipher_ctx *ctx; - const EVP_CIPHER *cipher; - ERL_NIF_TERM ret; - - if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) - || !enif_inspect_binary(env, argv[1], &ivec_bin) - || ivec_bin.size != 16) { - return enif_make_badarg(env); - } - - switch (key_bin.size) - { - case 16: cipher = EVP_aes_128_ctr(); break; - case 24: cipher = EVP_aes_192_ctr(); break; - case 32: cipher = EVP_aes_256_ctr(); break; - default: return enif_make_badarg(env); - } - - ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); - ctx->ctx = EVP_CIPHER_CTX_new(); - EVP_CipherInit_ex(ctx->ctx, cipher, NULL, - key_bin.data, ivec_bin.data, 1); - EVP_CIPHER_CTX_set_padding(ctx->ctx, 0); - ret = enif_make_resource(env, ctx); - enif_release_resource(ctx); - return ret; -} -static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Context, Data) */ - struct evp_cipher_ctx *ctx, *new_ctx; - ErlNifBinary data_bin; - ERL_NIF_TERM ret, cipher_term; - unsigned char *out; - int outl = 0; - - if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx) - || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) { - return enif_make_badarg(env); - } - new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); - new_ctx->ctx = EVP_CIPHER_CTX_new(); - EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx); - out = enif_make_new_binary(env, data_bin.size, &cipher_term); - EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size); - ASSERT(outl == data_bin.size); - - ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term); - enif_release_resource(new_ctx); - CONSUME_REDS(env,data_bin); - return ret; -} - -#else /* if not HAVE_EVP_AES_CTR */ - -static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, IVec) */ - ErlNifBinary key_bin, ivec_bin; - ERL_NIF_TERM ecount_bin; - - if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) - || !enif_inspect_binary(env, argv[1], &ivec_bin) - || !(key_bin.size == 16 || key_bin.size == 24 || key_bin.size ==32) - || ivec_bin.size != 16) { - return enif_make_badarg(env); - } - - memset(enif_make_new_binary(env, AES_BLOCK_SIZE, &ecount_bin), - 0, AES_BLOCK_SIZE); - return enif_make_tuple4(env, argv[0], argv[1], ecount_bin, enif_make_int(env, 0)); -} - -static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* ({Key, IVec, ECount, Num}, Data) */ - ErlNifBinary key_bin, ivec_bin, text_bin, ecount_bin; - AES_KEY aes_key; - unsigned int num; - ERL_NIF_TERM ret, num2_term, cipher_term, ivec2_term, ecount2_term, new_state_term; - int state_arity; - const ERL_NIF_TERM *state_term; - unsigned char * ivec2_buf; - unsigned char * ecount2_buf; - - if (!enif_get_tuple(env, argv[0], &state_arity, &state_term) - || state_arity != 4 - || !enif_inspect_iolist_as_binary(env, state_term[0], &key_bin) - || AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key) != 0 - || !enif_inspect_binary(env, state_term[1], &ivec_bin) || ivec_bin.size != 16 - || !enif_inspect_binary(env, state_term[2], &ecount_bin) || ecount_bin.size != AES_BLOCK_SIZE - || !enif_get_uint(env, state_term[3], &num) - || !enif_inspect_iolist_as_binary(env, argv[1], &text_bin)) { - return enif_make_badarg(env); - } - - ivec2_buf = enif_make_new_binary(env, ivec_bin.size, &ivec2_term); - ecount2_buf = enif_make_new_binary(env, ecount_bin.size, &ecount2_term); - - memcpy(ivec2_buf, ivec_bin.data, 16); - memcpy(ecount2_buf, ecount_bin.data, ecount_bin.size); - - AES_ctr128_encrypt((unsigned char *) text_bin.data, - enif_make_new_binary(env, text_bin.size, &cipher_term), - text_bin.size, &aes_key, ivec2_buf, ecount2_buf, &num); - - num2_term = enif_make_uint(env, num); - new_state_term = enif_make_tuple4(env, state_term[0], ivec2_term, ecount2_term, num2_term); - ret = enif_make_tuple2(env, new_state_term, cipher_term); - CONSUME_REDS(env,text_bin); - return ret; -} -#endif /* !HAVE_EVP_AES_CTR */ - -static ERL_NIF_TERM aead_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type,Key,Iv,AAD,In) */ -#if defined(HAVE_AEAD) - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher = NULL; - ErlNifBinary key, iv, aad, in; - unsigned int tag_len; - unsigned char *outp, *tagp; - ERL_NIF_TERM type, out, out_tag; - int len, ctx_ctrl_set_ivlen, ctx_ctrl_get_tag; - - type = argv[0]; - - if (!enif_is_atom(env, type) - || !enif_inspect_iolist_as_binary(env, argv[1], &key) - || !enif_inspect_binary(env, argv[2], &iv) - || !enif_inspect_iolist_as_binary(env, argv[3], &aad) - || !enif_inspect_iolist_as_binary(env, argv[4], &in) - || !enif_get_uint(env, argv[5], &tag_len)) { - return enif_make_badarg(env); - } - - /* Use cipher_type some day. Must check block_encrypt|decrypt first */ -#if defined(HAVE_GCM) - if (type == atom_aes_gcm) { - if ((iv.size > 0) - && (1 <= tag_len && tag_len <= 16)) { - ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN; - ctx_ctrl_get_tag = EVP_CTRL_GCM_GET_TAG; - if (key.size == 16) cipher = EVP_aes_128_gcm(); - else if (key.size == 24) cipher = EVP_aes_192_gcm(); - else if (key.size == 32) cipher = EVP_aes_256_gcm(); - else enif_make_badarg(env); - } else - enif_make_badarg(env); - } else -#endif -#if defined(HAVE_CCM) - if (type == atom_aes_ccm) { - if ((7 <= iv.size && iv.size <= 13) - && (4 <= tag_len && tag_len <= 16) - && ((tag_len & 1) == 0) - ) { - ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN; - ctx_ctrl_get_tag = EVP_CTRL_CCM_GET_TAG; - if (key.size == 16) cipher = EVP_aes_128_ccm(); - else if (key.size == 24) cipher = EVP_aes_192_ccm(); - else if (key.size == 32) cipher = EVP_aes_256_ccm(); - else enif_make_badarg(env); - } else - enif_make_badarg(env); - } else -#endif -#if defined(HAVE_CHACHA20_POLY1305) - if (type == atom_chacha20_poly1305) { - if ((key.size == 32) - && (1 <= iv.size && iv.size <= 16) - && (tag_len == 16) - ) { - ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN; - ctx_ctrl_get_tag = EVP_CTRL_AEAD_GET_TAG, - cipher = EVP_chacha20_poly1305(); - } else enif_make_badarg(env); - } else -#endif - return enif_raise_exception(env, atom_notsup); - - ctx = EVP_CIPHER_CTX_new(); - if (EVP_EncryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err; - if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err; - -#if defined(HAVE_CCM) - if (type == atom_aes_ccm) { - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_len, NULL) != 1) goto out_err; - if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err; - if (EVP_EncryptUpdate(ctx, NULL, &len, NULL, in.size) != 1) goto out_err; - } else -#endif - if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err; - - if (EVP_EncryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err; - - outp = enif_make_new_binary(env, in.size, &out); - - if (EVP_EncryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err; - if (EVP_EncryptFinal_ex(ctx, outp/*+len*/, &len) != 1) goto out_err; - - tagp = enif_make_new_binary(env, tag_len, &out_tag); - - if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_get_tag, tag_len, tagp) != 1) goto out_err; - - EVP_CIPHER_CTX_free(ctx); - CONSUME_REDS(env, in); - return enif_make_tuple2(env, out, out_tag); - -out_err: - EVP_CIPHER_CTX_free(ctx); - return atom_error; - -#else - return enif_raise_exception(env, atom_notsup); -#endif -} - -static ERL_NIF_TERM aead_decrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type,Key,Iv,AAD,In,Tag) */ -#if defined(HAVE_AEAD) - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher = NULL; - ErlNifBinary key, iv, aad, in, tag; - unsigned char *outp; - ERL_NIF_TERM type, out; - int len, ctx_ctrl_set_ivlen, ctx_ctrl_set_tag; - - type = argv[0]; -#if defined(HAVE_GCM_EVP_DECRYPT_BUG) - if (type == atom_aes_gcm) - return aes_gcm_decrypt_NO_EVP(env, argc, argv); -#endif - - if (!enif_is_atom(env, type) - || !enif_inspect_iolist_as_binary(env, argv[1], &key) - || !enif_inspect_binary(env, argv[2], &iv) - || !enif_inspect_iolist_as_binary(env, argv[3], &aad) - || !enif_inspect_iolist_as_binary(env, argv[4], &in) - || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) { - return enif_make_badarg(env); - } - - /* Use cipher_type some day. Must check block_encrypt|decrypt first */ -#if defined(HAVE_GCM) - if (type == atom_aes_gcm) { - if (iv.size > 0) { - ctx_ctrl_set_ivlen = EVP_CTRL_GCM_SET_IVLEN; - ctx_ctrl_set_tag = EVP_CTRL_GCM_SET_TAG; - if (key.size == 16) cipher = EVP_aes_128_gcm(); - else if (key.size == 24) cipher = EVP_aes_192_gcm(); - else if (key.size == 32) cipher = EVP_aes_256_gcm(); - else enif_make_badarg(env); - } else - enif_make_badarg(env); - } else -#endif -#if defined(HAVE_CCM) - if (type == atom_aes_ccm) { - if (iv.size > 0) { - ctx_ctrl_set_ivlen = EVP_CTRL_CCM_SET_IVLEN; - if (key.size == 16) cipher = EVP_aes_128_ccm(); - else if (key.size == 24) cipher = EVP_aes_192_ccm(); - else if (key.size == 32) cipher = EVP_aes_256_ccm(); - else enif_make_badarg(env); - } else - enif_make_badarg(env); - } else -#endif -#if defined(HAVE_CHACHA20_POLY1305) - if (type == atom_chacha20_poly1305) { - if ((key.size == 32) - && (1 <= iv.size && iv.size <= 16) - && tag.size == 16 - ) { - ctx_ctrl_set_ivlen = EVP_CTRL_AEAD_SET_IVLEN; - ctx_ctrl_set_tag = EVP_CTRL_AEAD_SET_TAG; - cipher = EVP_chacha20_poly1305(); - } else enif_make_badarg(env); - } else -#endif - return enif_raise_exception(env, atom_notsup); - - outp = enif_make_new_binary(env, in.size, &out); - - ctx = EVP_CIPHER_CTX_new(); - if (EVP_DecryptInit_ex(ctx, cipher, NULL, NULL, NULL) != 1) goto out_err; - if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_ivlen, iv.size, NULL) != 1) goto out_err; - -#if defined(HAVE_CCM) - if (type == atom_aes_ccm) { - if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag.size, tag.data) != 1) goto out_err; - } -#endif - - if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.data, iv.data) != 1) goto out_err; - -#if defined(HAVE_CCM) - if (type == atom_aes_ccm) { - if (1 != EVP_DecryptUpdate(ctx, NULL, &len, NULL, in.size)) goto out_err; - } -#endif - - if (EVP_DecryptUpdate(ctx, NULL, &len, aad.data, aad.size) != 1) goto out_err; - if (EVP_DecryptUpdate(ctx, outp, &len, in.data, in.size) != 1) goto out_err; - -#if defined(HAVE_GCM) || defined(HAVE_CHACHA20_POLY1305) - if (type == atom_aes_gcm) { - if (EVP_CIPHER_CTX_ctrl(ctx, ctx_ctrl_set_tag, tag.size, tag.data) != 1) goto out_err; - if (EVP_DecryptFinal_ex(ctx, outp+len, &len) != 1) goto out_err; - } -#endif - EVP_CIPHER_CTX_free(ctx); - - CONSUME_REDS(env, in); - return out; - -out_err: - EVP_CIPHER_CTX_free(ctx); - return atom_error; -#else - return enif_raise_exception(env, atom_notsup); -#endif -} - -#ifdef HAVE_GCM_EVP_DECRYPT_BUG -static ERL_NIF_TERM aes_gcm_decrypt_NO_EVP(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Type,Key,Iv,AAD,In,Tag) */ - GCM128_CONTEXT *ctx; - ErlNifBinary key, iv, aad, in, tag; - AES_KEY aes_key; - unsigned char *outp; - ERL_NIF_TERM out; - - if (!enif_inspect_iolist_as_binary(env, argv[1], &key) - || AES_set_encrypt_key(key.data, key.size*8, &aes_key) != 0 - || !enif_inspect_binary(env, argv[2], &iv) || iv.size == 0 - || !enif_inspect_iolist_as_binary(env, argv[3], &aad) - || !enif_inspect_iolist_as_binary(env, argv[4], &in) - || !enif_inspect_iolist_as_binary(env, argv[5], &tag)) { - return enif_make_badarg(env); - } - - if (!(ctx = CRYPTO_gcm128_new(&aes_key, (block128_f)AES_encrypt))) - return atom_error; - - CRYPTO_gcm128_setiv(ctx, iv.data, iv.size); - - if (CRYPTO_gcm128_aad(ctx, aad.data, aad.size)) - goto out_err; - - outp = enif_make_new_binary(env, in.size, &out); - - /* decrypt */ - if (CRYPTO_gcm128_decrypt(ctx, in.data, outp, in.size)) - goto out_err; - - /* calculate and check the tag */ - if (CRYPTO_gcm128_finish(ctx, tag.data, tag.size)) - goto out_err; - - CRYPTO_gcm128_release(ctx); - CONSUME_REDS(env, in); - - return out; - -out_err: - CRYPTO_gcm128_release(ctx); - return atom_error; -} -#endif /* HAVE_GCM_EVP_DECRYPT_BUG */ - - -static ERL_NIF_TERM chacha20_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key, IV) */ -#if defined(HAVE_CHACHA20) - ErlNifBinary key_bin, ivec_bin; - struct evp_cipher_ctx *ctx; - const EVP_CIPHER *cipher; - ERL_NIF_TERM ret; - - if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin) - || !enif_inspect_binary(env, argv[1], &ivec_bin) - || key_bin.size != 32 - || ivec_bin.size != 16) { - return enif_make_badarg(env); - } - - cipher = EVP_chacha20(); - - ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); - ctx->ctx = EVP_CIPHER_CTX_new(); - - - EVP_CipherInit_ex(ctx->ctx, cipher, NULL, - key_bin.data, ivec_bin.data, 1); - EVP_CIPHER_CTX_set_padding(ctx->ctx, 0); - ret = enif_make_resource(env, ctx); - enif_release_resource(ctx); - return ret; -#else - return enif_raise_exception(env, atom_notsup); -#endif -}; - -static ERL_NIF_TERM chacha20_stream_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (State, Data) */ -#if defined(HAVE_CHACHA20) - struct evp_cipher_ctx *ctx, *new_ctx; - ErlNifBinary data_bin; - ERL_NIF_TERM ret, cipher_term; - unsigned char *out; - int outl = 0; - - if (!enif_get_resource(env, argv[0], evp_cipher_ctx_rtype, (void**)&ctx) - || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) { - return enif_make_badarg(env); - } - new_ctx = enif_alloc_resource(evp_cipher_ctx_rtype, sizeof(struct evp_cipher_ctx)); - new_ctx->ctx = EVP_CIPHER_CTX_new(); - EVP_CIPHER_CTX_copy(new_ctx->ctx, ctx->ctx); - out = enif_make_new_binary(env, data_bin.size, &cipher_term); - EVP_CipherUpdate(new_ctx->ctx, out, &outl, data_bin.data, data_bin.size); - ASSERT(outl == data_bin.size); - - ret = enif_make_tuple2(env, enif_make_resource(env, new_ctx), cipher_term); - enif_release_resource(new_ctx); - CONSUME_REDS(env,data_bin); - return ret; -#else - return enif_raise_exception(env, atom_notsup); -#endif -}; - - -static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Bytes) */ - unsigned bytes; - unsigned char* data; - ERL_NIF_TERM ret; - - if (!enif_get_uint(env, argv[0], &bytes)) { - return enif_make_badarg(env); - } - data = enif_make_new_binary(env, bytes, &ret); - if ( RAND_bytes(data, bytes) != 1) { - return atom_false; - } - ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes); - return ret; -} - - -static int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp) -{ - ErlNifBinary bin; - int sz; - if (!enif_inspect_binary(env,term,&bin)) { - return 0; - } - ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size); - sz = bin.size - 4; - if (sz < 0 || get_int32(bin.data) != sz) { - return 0; - } - *bnp = BN_bin2bn(bin.data+4, sz, NULL); - return 1; -} - -static int get_bn_from_bin(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp) -{ - ErlNifBinary bin; - if (!enif_inspect_binary(env,term,&bin)) { - return 0; - } - ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size); - *bnp = BN_bin2bn(bin.data, bin.size, NULL); - return 1; -} - -static ERL_NIF_TERM bin_from_bn(ErlNifEnv* env, const BIGNUM *bn) -{ - int bn_len; - unsigned char *bin_ptr; - ERL_NIF_TERM term; - - /* Copy the bignum into an erlang binary. */ - bn_len = BN_num_bytes(bn); - bin_ptr = enif_make_new_binary(env, bn_len, &term); - BN_bn2bin(bn, bin_ptr); - - return term; -} - -static ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Range) */ - BIGNUM *bn_range, *bn_rand; - ERL_NIF_TERM ret; - - if(!get_bn_from_bin(env, argv[0], &bn_range)) { - return enif_make_badarg(env); - } - - bn_rand = BN_new(); - if (BN_rand_range(bn_rand, bn_range) != 1) { - ret = atom_false; - } - else { - ret = bin_from_bn(env, bn_rand); - } - BN_free(bn_rand); - BN_free(bn_range); - return ret; -} - -static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Lo,Hi) */ - BIGNUM *bn_from = NULL, *bn_to, *bn_rand; - unsigned char* data; - unsigned dlen; - ERL_NIF_TERM ret; - - if (!get_bn_from_mpint(env, argv[0], &bn_from) - || !get_bn_from_mpint(env, argv[1], &bn_rand)) { - if (bn_from) BN_free(bn_from); - return enif_make_badarg(env); - } - - bn_to = BN_new(); - BN_sub(bn_to, bn_rand, bn_from); - BN_pseudo_rand_range(bn_rand, bn_to); - BN_add(bn_rand, bn_rand, bn_from); - dlen = BN_num_bytes(bn_rand); - data = enif_make_new_binary(env, dlen+4, &ret); - put_int32(data, dlen); - BN_bn2bin(bn_rand, data+4); - ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen); - BN_free(bn_rand); - BN_free(bn_from); - BN_free(bn_to); - return ret; -} - -static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Base,Exponent,Modulo,bin_hdr) */ - BIGNUM *bn_base=NULL, *bn_exponent=NULL, *bn_modulo=NULL, *bn_result; - BN_CTX *bn_ctx; - unsigned char* ptr; - unsigned dlen; - unsigned bin_hdr; /* return type: 0=plain binary, 4: mpint */ - unsigned extra_byte; - ERL_NIF_TERM ret; - - if (!get_bn_from_bin(env, argv[0], &bn_base) - || !get_bn_from_bin(env, argv[1], &bn_exponent) - || !get_bn_from_bin(env, argv[2], &bn_modulo) - || !enif_get_uint(env,argv[3],&bin_hdr) || (bin_hdr & ~4)) { - - if (bn_base) BN_free(bn_base); - if (bn_exponent) BN_free(bn_exponent); - if (bn_modulo) BN_free(bn_modulo); - return enif_make_badarg(env); - } - bn_result = BN_new(); - bn_ctx = BN_CTX_new(); - BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx); - dlen = BN_num_bytes(bn_result); - extra_byte = bin_hdr && BN_is_bit_set(bn_result, dlen*8-1); - ptr = enif_make_new_binary(env, bin_hdr+extra_byte+dlen, &ret); - if (bin_hdr) { - put_int32(ptr, extra_byte+dlen); - ptr[4] = 0; /* extra zeroed byte to ensure a positive mpint */ - ptr += bin_hdr + extra_byte; - } - BN_bn2bin(bn_result, ptr); - BN_free(bn_result); - BN_CTX_free(bn_ctx); - BN_free(bn_modulo); - BN_free(bn_exponent); - BN_free(bn_base); - return ret; -} - -static void init_digest_types(ErlNifEnv* env) -{ - struct digest_type_t* p = digest_types; - - for (p = digest_types; p->type.str; p++) { - p->type.atom = enif_make_atom(env, p->type.str); - if (p->md.funcp) - p->md.p = p->md.funcp(); - } - p->type.atom = atom_false; /* end marker */ -} - -static void init_cipher_types(ErlNifEnv* env) -{ - struct cipher_type_t* p = cipher_types; - - for (p = cipher_types; p->type.str; p++) { - p->type.atom = enif_make_atom(env, p->type.str); - if (p->cipher.funcp) - p->cipher.p = p->cipher.funcp(); - } - p->type.atom = atom_false; /* end marker */ -} - -static struct digest_type_t* get_digest_type(ERL_NIF_TERM type) -{ - struct digest_type_t* p = NULL; - for (p = digest_types; p->type.atom != atom_false; p++) { - if (type == p->type.atom) { - return p; - } - } - return NULL; -} - -static struct cipher_type_t* get_cipher_type(ERL_NIF_TERM type, size_t key_len) -{ - struct cipher_type_t* p = NULL; - for (p = cipher_types; p->type.atom != atom_false; p++) { - if (type == p->type.atom && (!p->key_len || key_len == p->key_len)) { - return p; - } - } - return NULL; -} - - -static ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Data1, Data2) */ - ErlNifBinary d1, d2; - unsigned char* ret_ptr; - int i; - ERL_NIF_TERM ret; - - if (!enif_inspect_iolist_as_binary(env,argv[0], &d1) - || !enif_inspect_iolist_as_binary(env,argv[1], &d2) - || d1.size != d2.size) { - return enif_make_badarg(env); - } - ret_ptr = enif_make_new_binary(env, d1.size, &ret); - - for (i=0; i<d1.size; i++) { - ret_ptr[i] = d1.data[i] ^ d2.data[i]; - } - CONSUME_REDS(env,d1); - return ret; -} - -static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Key) */ -#ifndef OPENSSL_NO_RC4 - ErlNifBinary key; - ERL_NIF_TERM ret; - - CHECK_NO_FIPS_MODE(); - - if (!enif_inspect_iolist_as_binary(env,argv[0], &key)) { - return enif_make_badarg(env); - } - RC4_set_key((RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret), - key.size, key.data); - return ret; -#else - return enif_raise_exception(env, atom_notsup); -#endif -} - -static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (State, Data) */ -#ifndef OPENSSL_NO_RC4 - ErlNifBinary state, data; - RC4_KEY* rc4_key; - ERL_NIF_TERM new_state, new_data; - - CHECK_NO_FIPS_MODE(); - - if (!enif_inspect_iolist_as_binary(env,argv[0], &state) - || state.size != sizeof(RC4_KEY) - || !enif_inspect_iolist_as_binary(env,argv[1], &data)) { - return enif_make_badarg(env); - } - rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state); - memcpy(rc4_key, state.data, sizeof(RC4_KEY)); - RC4(rc4_key, data.size, data.data, - enif_make_new_binary(env, data.size, &new_data)); - CONSUME_REDS(env,data); - return enif_make_tuple2(env,new_state,new_data); -#else - return enif_raise_exception(env, atom_notsup); -#endif -} - -static int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa) -{ - /* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */ - ERL_NIF_TERM head, tail; - BIGNUM *e, *n, *d; - BIGNUM *p, *q; - BIGNUM *dmp1, *dmq1, *iqmp; - - if (!enif_get_list_cell(env, key, &head, &tail) - || !get_bn_from_bin(env, head, &e) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &n) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &d)) { - return 0; - } - (void) RSA_set0_key(rsa, n, e, d); - if (enif_is_empty_list(env, tail)) { - return 1; - } - if (!enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &p) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &q) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dmp1) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dmq1) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &iqmp) - || !enif_is_empty_list(env, tail)) { - return 0; - } - (void) RSA_set0_factors(rsa, p, q); - (void) RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp); - return 1; -} - - -static int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa) -{ - /* key=[E,N] */ - ERL_NIF_TERM head, tail; - BIGNUM *e, *n; - - if (!enif_get_list_cell(env, key, &head, &tail) - || !get_bn_from_bin(env, head, &e) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &n) - || !enif_is_empty_list(env, tail)) { - return 0; - } - - (void) RSA_set0_key(rsa, n, e, NULL); - return 1; -} - -#ifdef HAVE_EDDSA - static int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey) -{ - /* key=[K] */ - ERL_NIF_TERM head, tail, tail2, algo; - ErlNifBinary bin; - int type; - - if (!enif_get_list_cell(env, key, &head, &tail) - || !enif_inspect_binary(env, head, &bin) - || !enif_get_list_cell(env, tail, &algo, &tail2) - || !enif_is_empty_list(env, tail2)) { - return 0; - } - if (algo == atom_ed25519) type = EVP_PKEY_ED25519; - else if (algo == atom_ed448) type = EVP_PKEY_ED448; - else - return 0; - - if (public) - *pkey = EVP_PKEY_new_raw_public_key(type, NULL, bin.data, bin.size); - else - *pkey = EVP_PKEY_new_raw_private_key(type, NULL, bin.data, bin.size); - - if (!pkey) - return 0; - return 1; -} -#endif - -static int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa) -{ - /* key=[P,Q,G,KEY] */ - ERL_NIF_TERM head, tail; - BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL; - BIGNUM *dummy_pub_key, *priv_key = NULL; - - if (!enif_get_list_cell(env, key, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_p) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_q) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_g) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &priv_key) - || !enif_is_empty_list(env,tail)) { - if (dsa_p) BN_free(dsa_p); - if (dsa_q) BN_free(dsa_q); - if (dsa_g) BN_free(dsa_g); - if (priv_key) BN_free(priv_key); - return 0; - } - - /* Note: DSA_set0_key() does not allow setting only the - * private key, although DSA_sign() does not use the - * public key. Work around this limitation by setting - * the public key to a copy of the private key. - */ - dummy_pub_key = BN_dup(priv_key); - - DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g); - DSA_set0_key(dsa, dummy_pub_key, priv_key); - return 1; -} - - -static int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa) -{ - /* key=[P, Q, G, Y] */ - ERL_NIF_TERM head, tail; - BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_y = NULL; - - if (!enif_get_list_cell(env, key, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_p) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_q) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_g) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dsa_y) - || !enif_is_empty_list(env,tail)) { - if (dsa_p) BN_free(dsa_p); - if (dsa_q) BN_free(dsa_q); - if (dsa_g) BN_free(dsa_g); - if (dsa_y) BN_free(dsa_y); - return 0; - } - - DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g); - DSA_set0_key(dsa, dsa_y, NULL); - return 1; -} - -/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */ -static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa) -{ - ERL_NIF_TERM result[8]; - const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; - - /* Return at least [E,N,D] */ - n = NULL; e = NULL; d = NULL; - RSA_get0_key(rsa, &n, &e, &d); - - result[0] = bin_from_bn(env, e); // Exponent E - result[1] = bin_from_bn(env, n); // Modulus N = p*q - result[2] = bin_from_bn(env, d); // Exponent D - - /* Check whether the optional additional parameters are available */ - p = NULL; q = NULL; - RSA_get0_factors(rsa, &p, &q); - dmp1 = NULL; dmq1 = NULL; iqmp = NULL; - RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); - - if (p && q && dmp1 && dmq1 && iqmp) { - result[3] = bin_from_bn(env, p); // Factor p - result[4] = bin_from_bn(env, q); // Factor q - result[5] = bin_from_bn(env, dmp1); // D mod (p-1) - result[6] = bin_from_bn(env, dmq1); // D mod (q-1) - result[7] = bin_from_bn(env, iqmp); // (1/q) mod p - - return enif_make_list_from_array(env, result, 8); - } else { - return enif_make_list_from_array(env, result, 3); - } -} - -static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt) -{ - ErlNifEnv *env = BN_GENCB_get_arg(ctxt); - - if (!enif_is_current_process_alive(env)) { - return 0; - } else { - return 1; - } -} - -static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (ModulusSize, PublicExponent) */ - int modulus_bits; - BIGNUM *pub_exp, *three; - RSA *rsa; - int success; - ERL_NIF_TERM result; - BN_GENCB *intr_cb; -#ifndef HAVE_OPAQUE_BN_GENCB - BN_GENCB intr_cb_buf; -#endif - - if (!enif_get_int(env, argv[0], &modulus_bits) || modulus_bits < 256) { - return enif_make_badarg(env); - } - - if (!get_bn_from_bin(env, argv[1], &pub_exp)) { - return enif_make_badarg(env); - } - - /* Make sure the public exponent is large enough (at least 3). - * Without this, RSA_generate_key_ex() can run forever. */ - three = BN_new(); - BN_set_word(three, 3); - success = BN_cmp(pub_exp, three); - BN_free(three); - if (success < 0) { - BN_free(pub_exp); - return enif_make_badarg(env); - } - - /* For large keys, prime generation can take many seconds. Set up - * the callback which we use to test whether the process has been - * interrupted. */ -#ifdef HAVE_OPAQUE_BN_GENCB - intr_cb = BN_GENCB_new(); -#else - intr_cb = &intr_cb_buf; -#endif - BN_GENCB_set(intr_cb, check_erlang_interrupt, env); - - rsa = RSA_new(); - success = RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb); - BN_free(pub_exp); - -#ifdef HAVE_OPAQUE_BN_GENCB - BN_GENCB_free(intr_cb); -#endif - - if (!success) { - RSA_free(rsa); - return atom_error; - } - - result = put_rsa_private_key(env, rsa); - RSA_free(rsa); - - return result; -} - -static ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - /* RSA key generation can take a long time (>1 sec for a large - * modulus), so schedule it as a CPU-bound operation. */ - return enif_schedule_nif(env, "rsa_generate_key", - ERL_NIF_DIRTY_JOB_CPU_BOUND, - rsa_generate_key, argc, argv); -} - -static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (PrivKey|undefined, DHParams=[P,G], Mpint, Len|0) */ - DH *dh_params = NULL; - int mpint; /* 0 or 4 */ - - { - ERL_NIF_TERM head, tail; - BIGNUM - *dh_p = NULL, - *dh_g = NULL, - *priv_key_in = NULL; - unsigned long - len = 0; - - if (!(get_bn_from_bin(env, argv[0], &priv_key_in) - || argv[0] == atom_undefined) - || !enif_get_list_cell(env, argv[1], &head, &tail) - || !get_bn_from_bin(env, head, &dh_p) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dh_g) - || !enif_is_empty_list(env, tail) - || !enif_get_int(env, argv[2], &mpint) || (mpint & ~4) - || !enif_get_ulong(env, argv[3], &len) - - /* Load dh_params with values to use by the generator. - Mem mgmnt transfered from dh_p etc to dh_params */ - || !(dh_params = DH_new()) - || (priv_key_in && !DH_set0_key(dh_params, NULL, priv_key_in)) - || !DH_set0_pqg(dh_params, dh_p, NULL, dh_g) - ) { - if (priv_key_in) BN_free(priv_key_in); - if (dh_p) BN_free(dh_p); - if (dh_g) BN_free(dh_g); - if (dh_params) DH_free(dh_params); - return enif_make_badarg(env); - } - - if (len) { - if (len < BN_num_bits(dh_p)) - DH_set_length(dh_params, len); - else { - if (priv_key_in) BN_free(priv_key_in); - if (dh_p) BN_free(dh_p); - if (dh_g) BN_free(dh_g); - if (dh_params) DH_free(dh_params); - return enif_make_badarg(env); - } - } - } - -#ifdef HAS_EVP_PKEY_CTX - { - EVP_PKEY_CTX *ctx; - EVP_PKEY *dhkey, *params; - int success; - - params = EVP_PKEY_new(); - success = EVP_PKEY_set1_DH(params, dh_params); /* set the key referenced by params to dh_params... */ - DH_free(dh_params); /* ...dh_params (and params) must be freed */ - if (!success) return atom_error; - - ctx = EVP_PKEY_CTX_new(params, NULL); - EVP_PKEY_free(params); - if (!ctx) { - return atom_error; - } - - if (!EVP_PKEY_keygen_init(ctx)) { - /* EVP_PKEY_CTX_free(ctx); */ - return atom_error; - } - - dhkey = EVP_PKEY_new(); - if (!EVP_PKEY_keygen(ctx, &dhkey)) { /* "performs a key generation operation, the ... */ - /*... generated key is written to ppkey." (=last arg) */ - /* EVP_PKEY_CTX_free(ctx); */ - /* EVP_PKEY_free(dhkey); */ - return atom_error; - } - - dh_params = EVP_PKEY_get1_DH(dhkey); /* return the referenced key. dh_params and dhkey must be freed */ - EVP_PKEY_free(dhkey); - if (!dh_params) { - /* EVP_PKEY_CTX_free(ctx); */ - return atom_error; - } - EVP_PKEY_CTX_free(ctx); - } -#else - if (!DH_generate_key(dh_params)) return atom_error; -#endif - { - unsigned char *pub_ptr, *prv_ptr; - int pub_len, prv_len; - ERL_NIF_TERM ret_pub, ret_prv; - const BIGNUM *pub_key_gen, *priv_key_gen; - - DH_get0_key(dh_params, - &pub_key_gen, &priv_key_gen); /* Get pub_key_gen and priv_key_gen. - "The values point to the internal representation of - the public key and private key values. This memory - should not be freed directly." says man */ - pub_len = BN_num_bytes(pub_key_gen); - prv_len = BN_num_bytes(priv_key_gen); - pub_ptr = enif_make_new_binary(env, pub_len+mpint, &ret_pub); - prv_ptr = enif_make_new_binary(env, prv_len+mpint, &ret_prv); - if (mpint) { - put_int32(pub_ptr, pub_len); pub_ptr += 4; - put_int32(prv_ptr, prv_len); prv_ptr += 4; - } - BN_bn2bin(pub_key_gen, pub_ptr); - BN_bn2bin(priv_key_gen, prv_ptr); - ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr, pub_len); - ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr, prv_len); - - DH_free(dh_params); - - return enif_make_tuple2(env, ret_pub, ret_prv); - } -} - -static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */ - BIGNUM *other_pub_key = NULL, - *dh_p = NULL, - *dh_g = NULL; - DH *dh_priv = DH_new(); - - /* Check the arguments and get - my private key (dh_priv), - the peer's public key (other_pub_key), - the parameters p & q - */ - - { - BIGNUM *dummy_pub_key = NULL, - *priv_key = NULL; - ERL_NIF_TERM head, tail; - - if (!get_bn_from_bin(env, argv[0], &other_pub_key) - || !get_bn_from_bin(env, argv[1], &priv_key) - || !enif_get_list_cell(env, argv[2], &head, &tail) - || !get_bn_from_bin(env, head, &dh_p) - || !enif_get_list_cell(env, tail, &head, &tail) - || !get_bn_from_bin(env, head, &dh_g) - || !enif_is_empty_list(env, tail) - - /* Note: DH_set0_key() does not allow setting only the - * private key, although DH_compute_key() does not use the - * public key. Work around this limitation by setting - * the public key to a copy of the private key. - */ - || !(dummy_pub_key = BN_dup(priv_key)) - || !DH_set0_key(dh_priv, dummy_pub_key, priv_key) - || !DH_set0_pqg(dh_priv, dh_p, NULL, dh_g) - ) { - if (dh_p) BN_free(dh_p); - if (dh_g) BN_free(dh_g); - if (other_pub_key) BN_free(other_pub_key); - if (dummy_pub_key) BN_free(dummy_pub_key); - if (priv_key) BN_free(priv_key); - return enif_make_badarg(env); - } - } - { - ErlNifBinary ret_bin; - int size; - - enif_alloc_binary(DH_size(dh_priv), &ret_bin); - size = DH_compute_key(ret_bin.data, other_pub_key, dh_priv); - BN_free(other_pub_key); - DH_free(dh_priv); - if (size<=0) { - enif_release_binary(&ret_bin); - return atom_error; - } - - if (size != ret_bin.size) enif_realloc_binary(&ret_bin, size); - return enif_make_binary(env, &ret_bin); - } -} - - -static ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Multiplier, Verifier, Generator, Exponent, Prime) */ - BIGNUM *bn_verifier = NULL; - BIGNUM *bn_exponent = NULL, *bn_generator = NULL, *bn_prime = NULL, *bn_multiplier = NULL, *bn_result; - BN_CTX *bn_ctx; - unsigned char* ptr; - unsigned dlen; - ERL_NIF_TERM ret; - - CHECK_NO_FIPS_MODE(); - - if (!get_bn_from_bin(env, argv[0], &bn_multiplier) - || !get_bn_from_bin(env, argv[1], &bn_verifier) - || !get_bn_from_bin(env, argv[2], &bn_generator) - || !get_bn_from_bin(env, argv[3], &bn_exponent) - || !get_bn_from_bin(env, argv[4], &bn_prime)) { - if (bn_multiplier) BN_free(bn_multiplier); - if (bn_verifier) BN_free(bn_verifier); - if (bn_generator) BN_free(bn_generator); - if (bn_exponent) BN_free(bn_exponent); - if (bn_prime) BN_free(bn_prime); - return enif_make_badarg(env); - } - - bn_result = BN_new(); - bn_ctx = BN_CTX_new(); - - /* B = k*v + g^b % N */ - - /* k * v */ - BN_mod_mul(bn_multiplier, bn_multiplier, bn_verifier, bn_prime, bn_ctx); - - /* g^b % N */ - BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx); - - /* k*v + g^b % N */ - BN_mod_add(bn_result, bn_result, bn_multiplier, bn_prime, bn_ctx); - - /* check that B % N != 0, reuse bn_multiplier */ - BN_nnmod(bn_multiplier, bn_result, bn_prime, bn_ctx); - if (BN_is_zero(bn_multiplier)) { - ret = atom_error; - } else { - dlen = BN_num_bytes(bn_result); - ptr = enif_make_new_binary(env, dlen, &ret); - BN_bn2bin(bn_result, ptr); - } - BN_free(bn_result); - BN_CTX_free(bn_ctx); - BN_free(bn_prime); - BN_free(bn_generator); - BN_free(bn_multiplier); - BN_free(bn_exponent); - BN_free(bn_verifier); - return ret; -} - -static ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (a, u, B, Multiplier, Prime, Exponent, Generator) */ -/* - <premaster secret> = (B - (k * g^x)) ^ (a + (u * x)) % N -*/ - BIGNUM *bn_exponent = NULL, *bn_a = NULL; - BIGNUM *bn_u = NULL, *bn_multiplier = NULL, *bn_exp2, - *bn_base, *bn_prime = NULL, *bn_generator = NULL, - *bn_B = NULL, *bn_result; - BN_CTX *bn_ctx; - unsigned char* ptr; - unsigned dlen; - ERL_NIF_TERM ret; - - CHECK_NO_FIPS_MODE(); - - if (!get_bn_from_bin(env, argv[0], &bn_a) - || !get_bn_from_bin(env, argv[1], &bn_u) - || !get_bn_from_bin(env, argv[2], &bn_B) - || !get_bn_from_bin(env, argv[3], &bn_multiplier) - || !get_bn_from_bin(env, argv[4], &bn_generator) - || !get_bn_from_bin(env, argv[5], &bn_exponent) - || !get_bn_from_bin(env, argv[6], &bn_prime)) - { - if (bn_exponent) BN_free(bn_exponent); - if (bn_a) BN_free(bn_a); - if (bn_u) BN_free(bn_u); - if (bn_B) BN_free(bn_B); - if (bn_multiplier) BN_free(bn_multiplier); - if (bn_generator) BN_free(bn_generator); - if (bn_prime) BN_free(bn_prime); - return enif_make_badarg(env); - } - - bn_ctx = BN_CTX_new(); - bn_result = BN_new(); - - /* check that B % N != 0 */ - BN_nnmod(bn_result, bn_B, bn_prime, bn_ctx); - if (BN_is_zero(bn_result)) { - BN_free(bn_exponent); - BN_free(bn_a); - BN_free(bn_generator); - BN_free(bn_prime); - BN_free(bn_u); - BN_free(bn_B); - BN_CTX_free(bn_ctx); - - return atom_error; - } - - /* (B - (k * g^x)) */ - bn_base = BN_new(); - BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx); - BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx); - BN_mod_sub(bn_base, bn_B, bn_result, bn_prime, bn_ctx); - - /* a + (u * x) */ - bn_exp2 = BN_new(); - BN_mul(bn_result, bn_u, bn_exponent, bn_ctx); - BN_add(bn_exp2, bn_a, bn_result); - - /* (B - (k * g^x)) ^ (a + (u * x)) % N */ - BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx); - - dlen = BN_num_bytes(bn_result); - ptr = enif_make_new_binary(env, dlen, &ret); - BN_bn2bin(bn_result, ptr); - BN_free(bn_result); - BN_CTX_free(bn_ctx); - - BN_free(bn_multiplier); - BN_free(bn_exp2); - BN_free(bn_u); - BN_free(bn_exponent); - BN_free(bn_a); - BN_free(bn_B); - BN_free(bn_base); - BN_free(bn_generator); - BN_free(bn_prime); - return ret; -} - -static ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Verifier, b, u, A, Prime) */ -/* - <premaster secret> = (A * v^u) ^ b % N -*/ - BIGNUM *bn_b = NULL, *bn_verifier = NULL; - BIGNUM *bn_prime = NULL, *bn_A = NULL, *bn_u = NULL, *bn_base, *bn_result; - BN_CTX *bn_ctx; - unsigned char* ptr; - unsigned dlen; - ERL_NIF_TERM ret; - - CHECK_NO_FIPS_MODE(); - - if (!get_bn_from_bin(env, argv[0], &bn_verifier) - || !get_bn_from_bin(env, argv[1], &bn_b) - || !get_bn_from_bin(env, argv[2], &bn_u) - || !get_bn_from_bin(env, argv[3], &bn_A) - || !get_bn_from_bin(env, argv[4], &bn_prime)) - { - if (bn_verifier) BN_free(bn_verifier); - if (bn_b) BN_free(bn_b); - if (bn_u) BN_free(bn_u); - if (bn_A) BN_free(bn_A); - if (bn_prime) BN_free(bn_prime); - return enif_make_badarg(env); - } - - bn_ctx = BN_CTX_new(); - bn_result = BN_new(); - - /* check that A % N != 0 */ - BN_nnmod(bn_result, bn_A, bn_prime, bn_ctx); - if (BN_is_zero(bn_result)) { - BN_free(bn_b); - BN_free(bn_verifier); - BN_free(bn_prime); - BN_free(bn_A); - BN_CTX_free(bn_ctx); - - return atom_error; - } - - /* (A * v^u) */ - bn_base = BN_new(); - BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx); - BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx); - - /* (A * v^u) ^ b % N */ - BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx); - - dlen = BN_num_bytes(bn_result); - ptr = enif_make_new_binary(env, dlen, &ret); - BN_bn2bin(bn_result, ptr); - BN_free(bn_result); - BN_CTX_free(bn_ctx); - - BN_free(bn_u); - BN_free(bn_base); - BN_free(bn_verifier); - BN_free(bn_prime); - BN_free(bn_A); - BN_free(bn_b); - return ret; -} - -#if defined(HAVE_EC) -static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg) -{ - EC_KEY *key = NULL; - int c_arity = -1; - const ERL_NIF_TERM* curve; - ErlNifBinary seed; - BIGNUM *p = NULL; - BIGNUM *a = NULL; - BIGNUM *b = NULL; - BIGNUM *bn_order = NULL; - BIGNUM *cofactor = NULL; - EC_GROUP *group = NULL; - EC_POINT *point = NULL; - - /* {Field, Prime, Point, Order, CoFactor} = Curve */ - if (enif_get_tuple(env,curve_arg,&c_arity,&curve) - && c_arity == 5 - && get_bn_from_bin(env, curve[3], &bn_order) - && (curve[4] != atom_none && get_bn_from_bin(env, curve[4], &cofactor))) { - - int f_arity = -1; - const ERL_NIF_TERM* field; - int p_arity = -1; - const ERL_NIF_TERM* prime; - - long field_bits; - - /* {A, B, Seed} = Prime */ - if (!enif_get_tuple(env,curve[1],&p_arity,&prime) - || !get_bn_from_bin(env, prime[0], &a) - || !get_bn_from_bin(env, prime[1], &b)) - goto out_err; - - if (!enif_get_tuple(env,curve[0],&f_arity,&field)) - goto out_err; - - if (f_arity == 2 && field[0] == atom_prime_field) { - /* {prime_field, Prime} */ - - if (!get_bn_from_bin(env, field[1], &p)) - goto out_err; - - if (BN_is_negative(p) || BN_is_zero(p)) - goto out_err; - - field_bits = BN_num_bits(p); - if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS) - goto out_err; - - /* create the EC_GROUP structure */ - group = EC_GROUP_new_curve_GFp(p, a, b, NULL); - - } else if (f_arity == 3 && field[0] == atom_characteristic_two_field) { -#if defined(OPENSSL_NO_EC2M) - enif_raise_exception(env, atom_notsup); - goto out_err; -#else - /* {characteristic_two_field, M, Basis} */ - - int b_arity = -1; - const ERL_NIF_TERM* basis; - unsigned int k1, k2, k3; - - if ((p = BN_new()) == NULL) - goto out_err; - - if (!enif_get_long(env, field[1], &field_bits) - || field_bits > OPENSSL_ECC_MAX_FIELD_BITS) - goto out_err; - - if (enif_get_tuple(env,field[2],&b_arity,&basis)) { - if (b_arity == 2 - && basis[0] == atom_tpbasis - && enif_get_uint(env, basis[1], &k1)) { - /* {tpbasis, k} = Basis */ - - if (!(field_bits > k1 && k1 > 0)) - goto out_err; - - /* create the polynomial */ - if (!BN_set_bit(p, (int)field_bits) - || !BN_set_bit(p, (int)k1) - || !BN_set_bit(p, 0)) - goto out_err; - - } else if (b_arity == 4 - && basis[0] == atom_ppbasis - && enif_get_uint(env, basis[1], &k1) - && enif_get_uint(env, basis[2], &k2) - && enif_get_uint(env, basis[3], &k3)) { - /* {ppbasis, k1, k2, k3} = Basis */ - - if (!(field_bits > k3 && k3 > k2 && k2 > k1 && k1 > 0)) - goto out_err; - - /* create the polynomial */ - if (!BN_set_bit(p, (int)field_bits) - || !BN_set_bit(p, (int)k1) - || !BN_set_bit(p, (int)k2) - || !BN_set_bit(p, (int)k3) - || !BN_set_bit(p, 0)) - goto out_err; - - } else - goto out_err; - } else if (field[2] == atom_onbasis) { - /* onbasis = Basis */ - /* no parameters */ - goto out_err; - - } else - goto out_err; - - group = EC_GROUP_new_curve_GF2m(p, a, b, NULL); -#endif - } else - goto out_err; - - if (!group) - goto out_err; - - if (enif_inspect_binary(env, prime[2], &seed)) { - EC_GROUP_set_seed(group, seed.data, seed.size); - } - - if (!term2point(env, curve[2], group, &point)) - goto out_err; - - if (BN_is_negative(bn_order) - || BN_is_zero(bn_order) - || BN_num_bits(bn_order) > (int)field_bits + 1) - goto out_err; - - if (!EC_GROUP_set_generator(group, point, bn_order, cofactor)) - goto out_err; - - EC_GROUP_set_asn1_flag(group, 0x0); - - key = EC_KEY_new(); - if (!key) - goto out_err; - EC_KEY_set_group(key, group); - } - else { - goto out_err; - } - - - goto out; - -out_err: - if (key) EC_KEY_free(key); - key = NULL; - -out: - /* some OpenSSL structures are mem-dup'ed into the key, - so we have to free our copies here */ - if (p) BN_free(p); - if (a) BN_free(a); - if (b) BN_free(b); - if (bn_order) BN_free(bn_order); - if (cofactor) BN_free(cofactor); - if (group) EC_GROUP_free(group); - if (point) EC_POINT_free(point); - - return key; -} - - -static ERL_NIF_TERM bn2term(ErlNifEnv* env, const BIGNUM *bn) -{ - unsigned dlen; - unsigned char* ptr; - ERL_NIF_TERM ret; - - if (!bn) - return atom_undefined; - - dlen = BN_num_bytes(bn); - ptr = enif_make_new_binary(env, dlen, &ret); - BN_bn2bin(bn, ptr); - ERL_VALGRIND_MAKE_MEM_DEFINED(ptr, dlen); - return ret; -} - -static ERL_NIF_TERM point2term(ErlNifEnv* env, - const EC_GROUP *group, - const EC_POINT *point, - point_conversion_form_t form) -{ - unsigned dlen; - ErlNifBinary bin; - - dlen = EC_POINT_point2oct(group, point, form, NULL, 0, NULL); - if (dlen == 0) - return atom_undefined; - - if (!enif_alloc_binary(dlen, &bin)) - return enif_make_badarg(env); - - if (!EC_POINT_point2oct(group, point, form, bin.data, bin.size, NULL)) { - enif_release_binary(&bin); - return enif_make_badarg(env); - } - ERL_VALGRIND_MAKE_MEM_DEFINED(bin.data, bin.size); - return enif_make_binary(env, &bin); -} - -static int term2point(ErlNifEnv* env, ERL_NIF_TERM term, - EC_GROUP *group, EC_POINT **pptr) -{ - int ret = 0; - ErlNifBinary bin; - EC_POINT *point; - - if (!enif_inspect_binary(env,term,&bin)) { - return 0; - } - - if ((*pptr = point = EC_POINT_new(group)) == NULL) { - return 0; - } - - /* set the point conversion form */ - EC_GROUP_set_point_conversion_form(group, (point_conversion_form_t)(bin.data[0] & ~0x01)); - - /* extract the ec point */ - if (!EC_POINT_oct2point(group, point, bin.data, bin.size, NULL)) { - EC_POINT_free(point); - *pptr = NULL; - } else - ret = 1; - - return ret; -} - -static int get_ec_key(ErlNifEnv* env, - ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub, - EC_KEY** res) -{ - EC_KEY *key = NULL; - BIGNUM *priv_key = NULL; - EC_POINT *pub_key = NULL; - EC_GROUP *group = NULL; - - if (!(priv == atom_undefined || get_bn_from_bin(env, priv, &priv_key)) - || !(pub == atom_undefined || enif_is_binary(env, pub))) { - goto out_err; - } - - key = ec_key_new(env, curve); - - if (!key) { - goto out_err; - } - - if (!group) - group = EC_GROUP_dup(EC_KEY_get0_group(key)); - - if (term2point(env, pub, group, &pub_key)) { - if (!EC_KEY_set_public_key(key, pub_key)) { - goto out_err; - } - } - if (priv != atom_undefined - && !BN_is_zero(priv_key)) { - if (!EC_KEY_set_private_key(key, priv_key)) - goto out_err; - - /* calculate public key (if necessary) */ - if (EC_KEY_get0_public_key(key) == NULL) - { - /* the public key was not included in the SEC1 private - * key => calculate the public key */ - pub_key = EC_POINT_new(group); - if (pub_key == NULL - || !EC_POINT_copy(pub_key, EC_GROUP_get0_generator(group)) - || !EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL) - || !EC_KEY_set_public_key(key, pub_key)) - goto out_err; - } - } - - goto out; - -out_err: - if (key) EC_KEY_free(key); - key = NULL; - -out: - /* some OpenSSL structures are mem-dup'ed into the key, - so we have to free our copies here */ - if (priv_key) BN_clear_free(priv_key); - if (pub_key) EC_POINT_free(pub_key); - if (group) EC_GROUP_free(group); - if (!key) - return 0; - *res = key; - return 1; -} -#endif /* HAVE_EC */ - -static ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ -#if defined(HAVE_EC) - EC_KEY *key = NULL; - const EC_GROUP *group; - const EC_POINT *public_key; - ERL_NIF_TERM priv_key; - ERL_NIF_TERM pub_key = atom_undefined; - - if (!get_ec_key(env, argv[0], argv[1], atom_undefined, &key)) - goto badarg; - - if (argv[1] == atom_undefined) { - if (!EC_KEY_generate_key(key)) - goto badarg; - } - - group = EC_KEY_get0_group(key); - public_key = EC_KEY_get0_public_key(key); - - if (group && public_key) { - pub_key = point2term(env, group, public_key, - EC_KEY_get_conv_form(key)); - } - priv_key = bn2term(env, EC_KEY_get0_private_key(key)); - EC_KEY_free(key); - return enif_make_tuple2(env, pub_key, priv_key); - -badarg: - if (key) - EC_KEY_free(key); - return make_badarg_maybe(env); -#else - return atom_notsup; -#endif -} - -/* - (_OthersPublicKey, _MyPrivateKey) - (_OthersPublicKey, _MyEC_Point) -*/ -static ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -/* (OtherPublicKey, Curve, My) */ -{ -#if defined(HAVE_EC) - ERL_NIF_TERM ret; - unsigned char *p; - EC_KEY* key = NULL; - int field_size = 0; - int i; - EC_GROUP *group; - const BIGNUM *priv_key; - EC_POINT *my_ecpoint = NULL; - EC_KEY *other_ecdh = NULL; - - if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key)) - return make_badarg_maybe(env); - - group = EC_GROUP_dup(EC_KEY_get0_group(key)); - priv_key = EC_KEY_get0_private_key(key); - - if (!term2point(env, argv[0], group, &my_ecpoint)) { - goto out_err; - } - - if ((other_ecdh = EC_KEY_new()) == NULL - || !EC_KEY_set_group(other_ecdh, group) - || !EC_KEY_set_private_key(other_ecdh, priv_key)) - goto out_err; - - field_size = EC_GROUP_get_degree(group); - if (field_size <= 0) - goto out_err; - - p = enif_make_new_binary(env, (field_size+7)/8, &ret); - i = ECDH_compute_key(p, (field_size+7)/8, my_ecpoint, other_ecdh, NULL); - - if (i < 0) - goto out_err; -out: - if (group) EC_GROUP_free(group); - if (my_ecpoint) EC_POINT_free(my_ecpoint); - if (other_ecdh) EC_KEY_free(other_ecdh); - if (key) EC_KEY_free(key); - - return ret; - -out_err: - ret = enif_make_badarg(env); - goto out; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) - /* (Curve, PeerBin, MyBin) */ -{ -#ifdef HAVE_ED_CURVE_DH - int type; - EVP_PKEY_CTX *ctx; - ErlNifBinary peer_bin, my_bin, key_bin; - EVP_PKEY *peer_key, *my_key; - size_t max_size; - - if (argv[0] == atom_x25519) type = EVP_PKEY_X25519; - else if (argv[0] == atom_x448) type = EVP_PKEY_X448; - else return enif_make_badarg(env); - - if (!enif_inspect_binary(env, argv[1], &peer_bin) || - !enif_inspect_binary(env, argv[2], &my_bin)) { - return enif_make_badarg(env); - } - - if (!(my_key = EVP_PKEY_new_raw_private_key(type, NULL, my_bin.data, my_bin.size)) || - !(ctx = EVP_PKEY_CTX_new(my_key, NULL))) { - return enif_make_badarg(env); - } - - if (!EVP_PKEY_derive_init(ctx)) { - return enif_make_badarg(env); - } - - if (!(peer_key = EVP_PKEY_new_raw_public_key(type, NULL, peer_bin.data, peer_bin.size)) || - !EVP_PKEY_derive_set_peer(ctx, peer_key)) { - return enif_make_badarg(env); - } - - if (!EVP_PKEY_derive(ctx, NULL, &max_size)) { - return enif_make_badarg(env); - } - - if (!enif_alloc_binary(max_size, &key_bin) || - !EVP_PKEY_derive(ctx, key_bin.data, &key_bin.size)) { - return enif_make_badarg(env); - } - - if (key_bin.size < max_size) { - size_t actual_size = key_bin.size; - if (!enif_realloc_binary(&key_bin, actual_size)) { - return enif_make_badarg(env); - } - } - - return enif_make_binary(env, &key_bin); -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -/* (Curve) */ -{ -#ifdef HAVE_ED_CURVE_DH - int type; - EVP_PKEY_CTX *ctx; - EVP_PKEY *pkey = NULL; - ERL_NIF_TERM ret_pub, ret_prv; - size_t key_len; - - if (argv[0] == atom_x25519) type = EVP_PKEY_X25519; - else if (argv[0] == atom_x448) type = EVP_PKEY_X448; - else return enif_make_badarg(env); - - if (!(ctx = EVP_PKEY_CTX_new_id(type, NULL))) return enif_make_badarg(env); - - if (!EVP_PKEY_keygen_init(ctx)) return atom_error; - if (!EVP_PKEY_keygen(ctx, &pkey)) return atom_error; - - if (!EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len)) return atom_error; - if (!EVP_PKEY_get_raw_public_key(pkey, - enif_make_new_binary(env, key_len, &ret_pub), - &key_len)) - return atom_error; - - if (!EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len)) return atom_error; - if (!EVP_PKEY_get_raw_private_key(pkey, - enif_make_new_binary(env, key_len, &ret_prv), - &key_len)) - return atom_error; - - return enif_make_tuple2(env, ret_pub, ret_prv); -#else - return atom_notsup; -#endif -} - -/*================================================================*/ -#define PKEY_BADARG -1 -#define PKEY_NOTSUP 0 -#define PKEY_OK 1 - -typedef struct PKeyCryptOptions { - const EVP_MD *rsa_mgf1_md; - ErlNifBinary rsa_oaep_label; - const EVP_MD *rsa_oaep_md; - int rsa_padding; - const EVP_MD *signature_md; -} PKeyCryptOptions; - -typedef struct PKeySignOptions { - const EVP_MD *rsa_mgf1_md; - int rsa_padding; - int rsa_pss_saltlen; -} PKeySignOptions; - -static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type, - const EVP_MD **md) -{ - struct digest_type_t *digp = NULL; - *md = NULL; - - if (type == atom_none && algorithm == atom_rsa) return PKEY_OK; -#ifdef HAVE_EDDSA - if (algorithm == atom_eddsa) return PKEY_OK; -#endif - digp = get_digest_type(type); - if (!digp) return PKEY_BADARG; - if (!digp->md.p) return PKEY_NOTSUP; - - *md = digp->md.p; - return PKEY_OK; -} - - -static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm, - ERL_NIF_TERM type, ERL_NIF_TERM data, - unsigned char *md_value, const EVP_MD **mdp, - unsigned char **tbsp, size_t *tbslenp) -{ - int i; - const ERL_NIF_TERM *tpl_terms; - int tpl_arity; - ErlNifBinary tbs_bin; - EVP_MD_CTX *mdctx; - const EVP_MD *md = *mdp; - unsigned char *tbs = *tbsp; - size_t tbslen = *tbslenp; - unsigned int tbsleni; - - if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK) { - return i; - } - if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) { - if (tpl_arity != 2 || tpl_terms[0] != atom_digest - || !enif_inspect_binary(env, tpl_terms[1], &tbs_bin) - || (md != NULL && tbs_bin.size != EVP_MD_size(md))) { - return PKEY_BADARG; - } - /* We have a digest (= hashed text) in tbs_bin */ - tbs = tbs_bin.data; - tbslen = tbs_bin.size; - } else if (md == NULL) { - if (!enif_inspect_binary(env, data, &tbs_bin)) { - return PKEY_BADARG; - } - /* md == NULL, that is no hashing because DigestType argument was atom_none */ - tbs = tbs_bin.data; - tbslen = tbs_bin.size; - } else { - if (!enif_inspect_binary(env, data, &tbs_bin)) { - return PKEY_BADARG; - } - /* We have the cleartext in tbs_bin and the hash algo info in md */ - tbs = md_value; - mdctx = EVP_MD_CTX_create(); - if (!mdctx) { - return PKEY_BADARG; - } - /* Looks well, now hash the plain text into a digest according to md */ - if (EVP_DigestInit_ex(mdctx, md, NULL) <= 0) { - EVP_MD_CTX_destroy(mdctx); - return PKEY_BADARG; - } - if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) <= 0) { - EVP_MD_CTX_destroy(mdctx); - return PKEY_BADARG; - } - if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) <= 0) { - EVP_MD_CTX_destroy(mdctx); - return PKEY_BADARG; - } - tbslen = (size_t)(tbsleni); - EVP_MD_CTX_destroy(mdctx); - } - - *mdp = md; - *tbsp = tbs; - *tbslenp = tbslen; - - return PKEY_OK; -} - - -static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, - const EVP_MD *md, PKeySignOptions *opt) -{ - ERL_NIF_TERM head, tail; - const ERL_NIF_TERM *tpl_terms; - int tpl_arity; - const EVP_MD *opt_md; - int i; - - if (!enif_is_list(env, options)) { - return PKEY_BADARG; - } - - /* defaults */ - if (algorithm == atom_rsa) { - opt->rsa_mgf1_md = NULL; - opt->rsa_padding = RSA_PKCS1_PADDING; - opt->rsa_pss_saltlen = -2; - } - - if (enif_is_empty_list(env, options)) { - return PKEY_OK; - } - - if (algorithm == atom_rsa) { - tail = options; - while (enif_get_list_cell(env, tail, &head, &tail)) { - if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) { - if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) { - i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); - if (i != PKEY_OK) { - return i; - } - opt->rsa_mgf1_md = opt_md; - } else if (tpl_terms[0] == atom_rsa_padding) { - if (tpl_terms[1] == atom_rsa_pkcs1_padding) { - opt->rsa_padding = RSA_PKCS1_PADDING; - } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) { -#ifdef HAVE_RSA_PKCS1_PSS_PADDING - opt->rsa_padding = RSA_PKCS1_PSS_PADDING; - if (opt->rsa_mgf1_md == NULL) { - opt->rsa_mgf1_md = md; - } -#else - return PKEY_NOTSUP; -#endif - } else if (tpl_terms[1] == atom_rsa_x931_padding) { - opt->rsa_padding = RSA_X931_PADDING; - } else if (tpl_terms[1] == atom_rsa_no_padding) { - opt->rsa_padding = RSA_NO_PADDING; - } else { - return PKEY_BADARG; - } - } else if (tpl_terms[0] == atom_rsa_pss_saltlen) { - if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen)) - || opt->rsa_pss_saltlen < -2) { - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } - } - } else { - return PKEY_BADARG; - } - - return PKEY_OK; -} - - -#ifdef HAS_ENGINE_SUPPORT -static int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e) -{ - ERL_NIF_TERM engine_res, key_id_term; - struct engine_ctx *ctx; - ErlNifBinary key_id_bin; - - if (!enif_get_map_value(env, key, atom_engine, &engine_res) || - !enif_get_resource(env, engine_res, engine_ctx_rtype, (void**)&ctx) || - !enif_get_map_value(env, key, atom_key_id, &key_id_term) || - !enif_inspect_binary(env, key_id_term, &key_id_bin)) { - return 0; - } - else { - *e = ctx->engine; - return zero_terminate(key_id_bin, id); - } -} - - -static char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key) { - ERL_NIF_TERM tmp_term; - ErlNifBinary pwd_bin; - char *pwd; - if (enif_get_map_value(env, key, atom_password, &tmp_term) && - enif_inspect_binary(env, tmp_term, &pwd_bin) && - zero_terminate(pwd_bin, &pwd) - ) return pwd; - - return NULL; -} - -static int zero_terminate(ErlNifBinary bin, char **buf) { - *buf = enif_alloc(bin.size+1); - if (!*buf) - return 0; - memcpy(*buf, bin.data, bin.size); - *(*buf+bin.size) = 0; - return 1; -} -#endif - -static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey) -{ - if (enif_is_map(env, key)) { -#ifdef HAS_ENGINE_SUPPORT - /* Use key stored in engine */ - ENGINE *e; - char *id; - char *password; - - if (!get_engine_and_key_id(env, key, &id, &e)) - return PKEY_BADARG; - password = get_key_password(env, key); - *pkey = ENGINE_load_private_key(e, id, NULL, password); - if (!*pkey) - return PKEY_BADARG; - enif_free(id); -#else - return PKEY_BADARG; -#endif - } - else if (algorithm == atom_rsa) { - RSA *rsa = RSA_new(); - - if (!get_rsa_private_key(env, key, rsa)) { - RSA_free(rsa); - return PKEY_BADARG; - } - - *pkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_RSA(*pkey, rsa)) { - EVP_PKEY_free(*pkey); - RSA_free(rsa); - return PKEY_BADARG; - } - } else if (algorithm == atom_ecdsa) { -#if defined(HAVE_EC) - EC_KEY *ec = NULL; - const ERL_NIF_TERM *tpl_terms; - int tpl_arity; - - if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2 - && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1]) - && get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec)) { - - *pkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) { - EVP_PKEY_free(*pkey); - EC_KEY_free(ec); - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } -#else - return PKEY_NOTSUP; -#endif - } else if (algorithm == atom_eddsa) { -#if defined(HAVE_EDDSA) - if (!get_eddsa_key(env, 0, key, pkey)) { - return PKEY_BADARG; - } -#else - return PKEY_NOTSUP; -#endif - } else if (algorithm == atom_dss) { - DSA *dsa = DSA_new(); - - if (!get_dss_private_key(env, key, dsa)) { - DSA_free(dsa); - return PKEY_BADARG; - } - - *pkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_DSA(*pkey, dsa)) { - EVP_PKEY_free(*pkey); - DSA_free(dsa); - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } - - return PKEY_OK; -} - - -static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, - EVP_PKEY **pkey) -{ - if (enif_is_map(env, key)) { -#ifdef HAS_ENGINE_SUPPORT - /* Use key stored in engine */ - ENGINE *e; - char *id; - char *password; - - if (!get_engine_and_key_id(env, key, &id, &e)) - return PKEY_BADARG; - password = get_key_password(env, key); - *pkey = ENGINE_load_public_key(e, id, NULL, password); - if (!pkey) - return PKEY_BADARG; - enif_free(id); -#else - return PKEY_BADARG; -#endif - } else if (algorithm == atom_rsa) { - RSA *rsa = RSA_new(); - - if (!get_rsa_public_key(env, key, rsa)) { - RSA_free(rsa); - return PKEY_BADARG; - } - - *pkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_RSA(*pkey, rsa)) { - EVP_PKEY_free(*pkey); - RSA_free(rsa); - return PKEY_BADARG; - } - } else if (algorithm == atom_ecdsa) { -#if defined(HAVE_EC) - EC_KEY *ec = NULL; - const ERL_NIF_TERM *tpl_terms; - int tpl_arity; - - if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2 - && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1]) - && get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec)) { - - *pkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) { - EVP_PKEY_free(*pkey); - EC_KEY_free(ec); - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } -#else - return PKEY_NOTSUP; -#endif - } else if (algorithm == atom_eddsa) { -#if defined(HAVE_EDDSA) - if (!get_eddsa_key(env, 1, key, pkey)) { - return PKEY_BADARG; - } -#else - return PKEY_NOTSUP; -#endif - } else if (algorithm == atom_dss) { - DSA *dsa = DSA_new(); - - if (!get_dss_public_key(env, key, dsa)) { - DSA_free(dsa); - return PKEY_BADARG; - } - - *pkey = EVP_PKEY_new(); - if (!EVP_PKEY_assign_DSA(*pkey, dsa)) { - EVP_PKEY_free(*pkey); - DSA_free(dsa); - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } - - return PKEY_OK; -} - -static ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */ - int i; - const EVP_MD *md = NULL; - unsigned char md_value[EVP_MAX_MD_SIZE]; - EVP_PKEY *pkey; -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX *ctx; - size_t siglen; -#else - unsigned len, siglen; -#endif - PKeySignOptions sig_opt; - ErlNifBinary sig_bin; /* signature */ - unsigned char *tbs; /* data to be signed */ - size_t tbslen; -/*char buf[1024]; -enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf); -enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf); -printf("\r\n"); -*/ - -#ifndef HAS_ENGINE_SUPPORT - if (enif_is_map(env, argv[3])) { - return atom_notsup; - } -#endif - - i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen); - if (i != PKEY_OK) { - if (i == PKEY_NOTSUP) - return atom_notsup; - else - return enif_make_badarg(env); - } - - i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt); - if (i != PKEY_OK) { - if (i == PKEY_NOTSUP) - return atom_notsup; - else - return enif_make_badarg(env); - } - - if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK) { - return enif_make_badarg(env); - } - -#ifdef HAS_EVP_PKEY_CTX - ctx = EVP_PKEY_CTX_new(pkey, NULL); - if (!ctx) goto badarg; - - if (argv[0] != atom_eddsa) { - if (EVP_PKEY_sign_init(ctx) <= 0) goto badarg; - if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg; - } - - if (argv[0] == atom_rsa) { - if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg; -# ifdef HAVE_RSA_PKCS1_PSS_PADDING - if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) { - if (sig_opt.rsa_mgf1_md != NULL) { -# ifdef HAVE_RSA_MGF1_MD - if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg; -# else - EVP_PKEY_CTX_free(ctx); - EVP_PKEY_free(pkey); - return atom_notsup; -# endif - } - if (sig_opt.rsa_pss_saltlen > -2 - && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0) - goto badarg; - } -#endif - } - - if (argv[0] == atom_eddsa) { -#ifdef HAVE_EDDSA - EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); - if (!EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey)) { - if (mdctx) EVP_MD_CTX_free(mdctx); - goto badarg; - } - - if (!EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen)) { - EVP_MD_CTX_free(mdctx); - goto badarg; - } - enif_alloc_binary(siglen, &sig_bin); - - if (!EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen)) { - EVP_MD_CTX_free(mdctx); - goto badarg; - } -#else - goto badarg; -#endif - } - else - { - if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) <= 0) goto badarg; - enif_alloc_binary(siglen, &sig_bin); - - if (md != NULL) { - ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md)); - } - i = EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen); - } - - EVP_PKEY_CTX_free(ctx); -#else -/*printf("Old interface\r\n"); - */ - if (argv[0] == atom_rsa) { - RSA *rsa = EVP_PKEY_get1_RSA(pkey); - enif_alloc_binary(RSA_size(rsa), &sig_bin); - len = EVP_MD_size(md); - ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len); - i = RSA_sign(md->type, tbs, len, sig_bin.data, &siglen, rsa); - RSA_free(rsa); - } else if (argv[0] == atom_dss) { - DSA *dsa = EVP_PKEY_get1_DSA(pkey); - enif_alloc_binary(DSA_size(dsa), &sig_bin); - len = EVP_MD_size(md); - ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len); - i = DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa); - DSA_free(dsa); - } else if (argv[0] == atom_ecdsa) { -#if defined(HAVE_EC) - EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); - enif_alloc_binary(ECDSA_size(ec), &sig_bin); - len = EVP_MD_size(md); - ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len); - i = ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec); - EC_KEY_free(ec); -#else - EVP_PKEY_free(pkey); - return atom_notsup; -#endif - } else { - goto badarg; - } -#endif - - EVP_PKEY_free(pkey); - if (i == 1) { - ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen); - if (siglen != sig_bin.size) { - enif_realloc_binary(&sig_bin, siglen); - ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen); - } - return enif_make_binary(env, &sig_bin); - } else { - enif_release_binary(&sig_bin); - return atom_error; - } - - badarg: -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX_free(ctx); -#endif - EVP_PKEY_free(pkey); - return enif_make_badarg(env); -} - - -static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */ - int i; - const EVP_MD *md = NULL; - unsigned char md_value[EVP_MAX_MD_SIZE]; - EVP_PKEY *pkey; -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX *ctx; -#else -#endif - PKeySignOptions sig_opt; - ErlNifBinary sig_bin; /* signature */ - unsigned char *tbs; /* data to be signed */ - size_t tbslen; - -#ifndef HAS_ENGINE_SUPPORT - if (enif_is_map(env, argv[4])) { - return atom_notsup; - } -#endif - - if (!enif_inspect_binary(env, argv[3], &sig_bin)) { - return enif_make_badarg(env); - } - - i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen); - if (i != PKEY_OK) { - if (i == PKEY_NOTSUP) - return atom_notsup; - else - return enif_make_badarg(env); - } - - i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt); - if (i != PKEY_OK) { - if (i == PKEY_NOTSUP) - return atom_notsup; - else - return enif_make_badarg(env); - } - - if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) { - return enif_make_badarg(env); - } - -#ifdef HAS_EVP_PKEY_CTX -/* printf("EVP interface\r\n"); - */ - ctx = EVP_PKEY_CTX_new(pkey, NULL); - if (!ctx) goto badarg; - - if (argv[0] != atom_eddsa) { - if (EVP_PKEY_verify_init(ctx) <= 0) goto badarg; - if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg; - } - - if (argv[0] == atom_rsa) { - if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg; - if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) { - if (sig_opt.rsa_mgf1_md != NULL) { -# ifdef HAVE_RSA_MGF1_MD - if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg; -# else - EVP_PKEY_CTX_free(ctx); - EVP_PKEY_free(pkey); - return atom_notsup; -# endif - } - if (sig_opt.rsa_pss_saltlen > -2 - && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0) - goto badarg; - } - } - - if (argv[0] == atom_eddsa) { -#ifdef HAVE_EDDSA - EVP_MD_CTX* mdctx = EVP_MD_CTX_create(); - - if (!EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey)) { - if (mdctx) EVP_MD_CTX_destroy(mdctx); - goto badarg; - } - - i = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen); - EVP_MD_CTX_destroy(mdctx); -#else - goto badarg; -#endif - } - else - { - if (md != NULL) { - ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md)); - } - i = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen); - } - - EVP_PKEY_CTX_free(ctx); -#else -/*printf("Old interface\r\n"); -*/ - if (argv[0] == atom_rsa) { - RSA *rsa = EVP_PKEY_get1_RSA(pkey); - i = RSA_verify(md->type, tbs, tbslen, sig_bin.data, sig_bin.size, rsa); - RSA_free(rsa); - } else if (argv[0] == atom_dss) { - DSA *dsa = EVP_PKEY_get1_DSA(pkey); - i = DSA_verify(0, tbs, tbslen, sig_bin.data, sig_bin.size, dsa); - DSA_free(dsa); - } else if (argv[0] == atom_ecdsa) { -#if defined(HAVE_EC) - EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); - i = ECDSA_verify(EVP_MD_type(md), tbs, tbslen, sig_bin.data, sig_bin.size, ec); - EC_KEY_free(ec); -#else - EVP_PKEY_free(pkey); - return atom_notsup; -#endif - } else { - goto badarg; - } -#endif - - EVP_PKEY_free(pkey); - if (i == 1) { - return atom_true; - } else { - return atom_false; - } - - badarg: -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX_free(ctx); -#endif - EVP_PKEY_free(pkey); - return enif_make_badarg(env); -} - - -/*--------------------------------*/ - -static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, - PKeyCryptOptions *opt) -{ - ERL_NIF_TERM head, tail; - const ERL_NIF_TERM *tpl_terms; - int tpl_arity; - const EVP_MD *opt_md; - int i; - - if (!enif_is_list(env, options)) { - return PKEY_BADARG; - } - - /* defaults */ - if (algorithm == atom_rsa) { - opt->rsa_mgf1_md = NULL; - opt->rsa_oaep_label.data = NULL; - opt->rsa_oaep_label.size = 0; - opt->rsa_oaep_md = NULL; - opt->rsa_padding = RSA_PKCS1_PADDING; - opt->signature_md = NULL; - } - - if (enif_is_empty_list(env, options)) { - return PKEY_OK; - } - - if (algorithm == atom_rsa) { - tail = options; - while (enif_get_list_cell(env, tail, &head, &tail)) { - if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) { - if (tpl_terms[0] == atom_rsa_padding - || tpl_terms[0] == atom_rsa_pad /* Compatibility */ - ) { - if (tpl_terms[1] == atom_rsa_pkcs1_padding) { - opt->rsa_padding = RSA_PKCS1_PADDING; -#ifdef HAVE_RSA_OAEP_PADDING - } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) { - opt->rsa_padding = RSA_PKCS1_OAEP_PADDING; -#endif -#ifdef HAVE_RSA_SSLV23_PADDING - } else if (tpl_terms[1] == atom_rsa_sslv23_padding) { - opt->rsa_padding = RSA_SSLV23_PADDING; -#endif - } else if (tpl_terms[1] == atom_rsa_x931_padding) { - opt->rsa_padding = RSA_X931_PADDING; - } else if (tpl_terms[1] == atom_rsa_no_padding) { - opt->rsa_padding = RSA_NO_PADDING; - } else { - return PKEY_BADARG; - } - } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) { - i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); - if (i != PKEY_OK) { - return i; - } - opt->signature_md = opt_md; - } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) { -#ifndef HAVE_RSA_MGF1_MD - if (tpl_terms[1] != atom_sha) - return PKEY_NOTSUP; -#endif - i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); - if (i != PKEY_OK) { - return i; - } - opt->rsa_mgf1_md = opt_md; - } else if (tpl_terms[0] == atom_rsa_oaep_label - && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) { -#ifdef HAVE_RSA_OAEP_MD - continue; -#else - return PKEY_NOTSUP; -#endif - } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) { -#ifndef HAVE_RSA_OAEP_MD - if (tpl_terms[1] != atom_sha) - return PKEY_NOTSUP; -#endif - i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); - if (i != PKEY_OK) { - return i; - } - opt->rsa_oaep_md = opt_md; - } else { - return PKEY_BADARG; - } - } else { - return PKEY_BADARG; - } - } - } else { - return PKEY_BADARG; - } - - return PKEY_OK; -} - -static ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) -{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */ - int i; - EVP_PKEY *pkey; -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX *ctx; -#else - RSA *rsa; -#endif - PKeyCryptOptions crypt_opt; - ErlNifBinary in_bin, out_bin, tmp_bin; - size_t outlen; -#ifdef HAVE_RSA_SSLV23_PADDING - size_t tmplen; -#endif - int is_private = (argv[4] == atom_true), - is_encrypt = (argv[5] == atom_true); - int algo_init = 0; - -/* char algo[1024]; */ - -#ifndef HAS_ENGINE_SUPPORT - if (enif_is_map(env, argv[2])) { - return atom_notsup; - } -#endif - - if (!enif_inspect_binary(env, argv[1], &in_bin)) { - return enif_make_badarg(env); - } - - i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt); - if (i != PKEY_OK) { - if (i == PKEY_NOTSUP) - return atom_notsup; - else - return enif_make_badarg(env); - } - - if (is_private) { - if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK) { - return enif_make_badarg(env); - } - } else { - if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK) { - return enif_make_badarg(env); - } - } - - out_bin.data = NULL; - out_bin.size = 0; - tmp_bin.data = NULL; - tmp_bin.size = 0; - -#ifdef HAS_EVP_PKEY_CTX - ctx = EVP_PKEY_CTX_new(pkey, NULL); - if (!ctx) goto badarg; - -/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1); */ - - if (is_private) { - if (is_encrypt) { - /* private encrypt */ - if ((algo_init=EVP_PKEY_sign_init(ctx)) <= 0) { - /* fprintf(stderr,"BADARG %s private encrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */ - goto badarg; - } - } else { - /* private decrypt */ - if ((algo_init=EVP_PKEY_decrypt_init(ctx)) <= 0) { - /* fprintf(stderr,"BADARG %s private decrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */ - goto badarg; - } - } - } else { - if (is_encrypt) { - /* public encrypt */ - if ((algo_init=EVP_PKEY_encrypt_init(ctx)) <= 0) { - /* fprintf(stderr,"BADARG %s public encrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */ - goto badarg; - } - } else { - /* public decrypt */ - if ((algo_init=EVP_PKEY_verify_recover_init(ctx)) <= 0) { - /* fprintf(stderr,"BADARG %s public decrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */ - goto badarg; - } - } - } - - if (argv[0] == atom_rsa) { - if (crypt_opt.signature_md != NULL - && EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) <= 0) - goto badarg; -#ifdef HAVE_RSA_SSLV23_PADDING - if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) { - if (is_encrypt) { - RSA *rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa == NULL) goto badarg; - tmplen = RSA_size(rsa); - if (!enif_alloc_binary(tmplen, &tmp_bin)) goto badarg; - if (RSA_padding_add_SSLv23(tmp_bin.data, tmplen, in_bin.data, in_bin.size) <= 0) - goto badarg; - in_bin = tmp_bin; - } - if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) goto badarg; - } else -#endif - { - if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) <= 0) goto badarg; - } -#ifdef HAVE_RSA_OAEP_MD - if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) { - if (crypt_opt.rsa_oaep_md != NULL - && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) <= 0) - goto badarg; - if (crypt_opt.rsa_mgf1_md != NULL - && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) <= 0) goto badarg; - if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) { - unsigned char *label_copy; - label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size); - if (label_copy == NULL) goto badarg; - memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data), - crypt_opt.rsa_oaep_label.size); - if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy, - crypt_opt.rsa_oaep_label.size) <= 0) { - OPENSSL_free(label_copy); - label_copy = NULL; - goto badarg; - } - } - } -#endif - } - - if (is_private) { - if (is_encrypt) { - /* private_encrypt */ - i = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size); - } else { - /* private_decrypt */ - i = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size); - } - } else { - if (is_encrypt) { - /* public_encrypt */ - i = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size); - } else { - /* public_decrypt */ - i = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size); - } - } - /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */ - - if (i != 1) goto badarg; - - enif_alloc_binary(outlen, &out_bin); - - if (is_private) { - if (is_encrypt) { - /* private_encrypt */ - i = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); - } else { - /* private_decrypt */ - i = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); - } - } else { - if (is_encrypt) { - /* public_encrypt */ - i = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); - } else { - /* public_decrypt */ - i = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); - } - } - -#else - /* Non-EVP cryptolib. Only support RSA */ - - if (argv[0] != atom_rsa) { - algo_init = -2; /* exitcode: notsup */ - goto badarg; - } - rsa = EVP_PKEY_get1_RSA(pkey); - enif_alloc_binary(RSA_size(rsa), &out_bin); - - if (is_private) { - if (is_encrypt) { - /* non-evp rsa private encrypt */ - ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size); - i = RSA_private_encrypt(in_bin.size, in_bin.data, - out_bin.data, rsa, crypt_opt.rsa_padding); - if (i > 0) { - ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); - } - } else { - /* non-evp rsa private decrypt */ - i = RSA_private_decrypt(in_bin.size, in_bin.data, - out_bin.data, rsa, crypt_opt.rsa_padding); - if (i > 0) { - ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); - enif_realloc_binary(&out_bin, i); - } - } - } else { - if (is_encrypt) { - /* non-evp rsa public encrypt */ - ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size); - i = RSA_public_encrypt(in_bin.size, in_bin.data, - out_bin.data, rsa, crypt_opt.rsa_padding); - if (i > 0) { - ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); - } - } else { - /* non-evp rsa public decrypt */ - i = RSA_public_decrypt(in_bin.size, in_bin.data, - out_bin.data, rsa, crypt_opt.rsa_padding); - if (i > 0) { - ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); - enif_realloc_binary(&out_bin, i); - } - } - } - - outlen = i; - RSA_free(rsa); -#endif - - if ((i > 0) && argv[0] == atom_rsa && !is_encrypt) { -#ifdef HAVE_RSA_SSLV23_PADDING - if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) { - RSA *rsa = EVP_PKEY_get1_RSA(pkey); - unsigned char *p; - if (rsa == NULL) goto badarg; - tmplen = RSA_size(rsa); - if (!enif_alloc_binary(tmplen, &tmp_bin)) { - RSA_free(rsa); - goto badarg; - } - p = out_bin.data; - p++; - i = RSA_padding_check_SSLv23(tmp_bin.data, tmplen, p, out_bin.size - 1, tmplen); - if (i >= 0) { - outlen = i; - in_bin = out_bin; - out_bin = tmp_bin; - tmp_bin = in_bin; - i = 1; - } - RSA_free(rsa); - } -#endif - } - - if (tmp_bin.data != NULL) { - enif_release_binary(&tmp_bin); - } - -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX_free(ctx); -#else -#endif - EVP_PKEY_free(pkey); - if (i > 0) { - ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen); - if (outlen != out_bin.size) { - enif_realloc_binary(&out_bin, outlen); - ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen); - } - return enif_make_binary(env, &out_bin); - } else { - enif_release_binary(&out_bin); - return atom_error; - } - - badarg: - if (out_bin.data != NULL) { - enif_release_binary(&out_bin); - } - if (tmp_bin.data != NULL) { - enif_release_binary(&tmp_bin); - } -#ifdef HAS_EVP_PKEY_CTX - EVP_PKEY_CTX_free(ctx); -#else -#endif - EVP_PKEY_free(pkey); - if (algo_init == -2) - return atom_notsup; - else - return enif_make_badarg(env); -} - - - -/*--------------------------------*/ -static ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ /* (Algorithm, PrivKey | KeyMap) */ - EVP_PKEY *pkey; - ERL_NIF_TERM alg = argv[0]; - ERL_NIF_TERM result[8]; - if (get_pkey_private_key(env, alg, argv[1], &pkey) != PKEY_OK) { - return enif_make_badarg(env); - } - - if (alg == atom_rsa) { - const BIGNUM *n = NULL, *e = NULL, *d = NULL; - RSA *rsa = EVP_PKEY_get1_RSA(pkey); - if (rsa) { - RSA_get0_key(rsa, &n, &e, &d); - result[0] = bin_from_bn(env, e); // Exponent E - result[1] = bin_from_bn(env, n); // Modulus N = p*q - EVP_PKEY_free(pkey); - return enif_make_list_from_array(env, result, 2); - } - - } else if (argv[0] == atom_dss) { - const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL; - DSA *dsa = EVP_PKEY_get1_DSA(pkey); - if (dsa) { - DSA_get0_pqg(dsa, &p, &q, &g); - DSA_get0_key(dsa, &pub_key, NULL); - result[0] = bin_from_bn(env, p); - result[1] = bin_from_bn(env, q); - result[2] = bin_from_bn(env, g); - result[3] = bin_from_bn(env, pub_key); - EVP_PKEY_free(pkey); - return enif_make_list_from_array(env, result, 4); - } - - } else if (argv[0] == atom_ecdsa) { -#if defined(HAVE_EC) - /* not yet implemented - EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); - if (ec) { - / * Example of result: - { - Curve = {Field, Prime, Point, Order, CoFactor} = - { - Field = {prime_field,<<255,...,255>>}, - Prime = {<<255,...,252>>, - <<90,...,75>>, - <<196,...,144>> - }, - Point = <<4,...,245>>, - Order = <<255,...,81>>, - CoFactor = <<1>> - }, - Key = <<151,...,62>> - } - or - { - Curve = - {characteristic_two_field, - M, - Basis = {tpbasis, _} - | {ppbasis, k1, k2, k3} - }, - Key - } - * / - EVP_PKEY_free(pkey); - return enif_make_list_from_array(env, ..., ...); - */ -#endif - } - - if (pkey) EVP_PKEY_free(pkey); - return enif_make_badarg(env); -} - -/*================================================================*/ - -static ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{ - ErlNifBinary seed_bin; - - if (!enif_inspect_binary(env, argv[0], &seed_bin)) - return enif_make_badarg(env); - RAND_seed(seed_bin.data,seed_bin.size); - return atom_ok; -} - -/*================================================================*/ -/* Engine */ -/*================================================================*/ -static ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (EngineId) */ -#ifdef HAS_ENGINE_SUPPORT - ERL_NIF_TERM ret; - ErlNifBinary engine_id_bin; - char *engine_id; - ENGINE *engine; - struct engine_ctx *ctx; - - // Get Engine Id - if(!enif_inspect_binary(env, argv[0], &engine_id_bin)) { - PRINTF_ERR0("engine_by_id_nif Leaved: badarg"); - return enif_make_badarg(env); - } else { - engine_id = enif_alloc(engine_id_bin.size+1); - (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size); - engine_id[engine_id_bin.size] = '\0'; - } - - engine = ENGINE_by_id(engine_id); - if(!engine) { - enif_free(engine_id); - PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}"); - return enif_make_tuple2(env, atom_error, atom_bad_engine_id); - } - - ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx)); - ctx->engine = engine; - ctx->id = engine_id; - - ret = enif_make_resource(env, ctx); - enif_release_resource(ctx); - - return enif_make_tuple2(env, atom_ok, ret); -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - ERL_NIF_TERM ret = atom_ok; - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_init_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - if (!ENGINE_init(ctx->engine)) { - //ERR_print_errors_fp(stderr); - PRINTF_ERR0("engine_init_nif Leaved: {error, engine_init_failed}"); - return enif_make_tuple2(env, atom_error, atom_engine_init_failed); - } - - return ret; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_free_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - ENGINE_free(ctx->engine); - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_finish_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - ENGINE_finish(ctx->engine); - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* () */ -#ifdef HAS_ENGINE_SUPPORT - ENGINE_load_dynamic(); - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine, Commands) */ -#ifdef HAS_ENGINE_SUPPORT - ERL_NIF_TERM ret = atom_ok; - unsigned int cmds_len = 0; - char **cmds = NULL; - struct engine_ctx *ctx; - int i, optional = 0; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine)); - - // Get Command List - if(!enif_get_list_length(env, argv[1], &cmds_len)) { - PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Bad Command List"); - return enif_make_badarg(env); - } else { - cmds_len *= 2; // Key-Value list from erlang - cmds = enif_alloc((cmds_len+1)*sizeof(char*)); - if(get_engine_load_cmd_list(env, argv[1], cmds, 0)) { - PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Couldn't read Command List"); - ret = enif_make_badarg(env); - goto error; - } - } - - if(!enif_get_int(env, argv[2], &optional)) { - PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter optional not an integer"); - return enif_make_badarg(env); - } - - for(i = 0; i < cmds_len; i+=2) { - PRINTF_ERR2("Cmd: %s:%s\r\n", - cmds[i] ? cmds[i] : "(NULL)", - cmds[i+1] ? cmds[i+1] : "(NULL)"); - if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) { - PRINTF_ERR2("Command failed: %s:%s\r\n", - cmds[i] ? cmds[i] : "(NULL)", - cmds[i+1] ? cmds[i+1] : "(NULL)"); - //ENGINE_free(ctx->engine); - ret = enif_make_tuple2(env, atom_error, atom_ctrl_cmd_failed); - PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: {error, ctrl_cmd_failed}"); - goto error; - } - } - - error: - for(i = 0; cmds != NULL && cmds[i] != NULL; i++) - enif_free(cmds[i]); - enif_free(cmds); - return ret; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_add_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - if (!ENGINE_add(ctx->engine)) { - PRINTF_ERR0("engine_add_nif Leaved: {error, add_engine_failed}"); - return enif_make_tuple2(env, atom_error, atom_add_engine_failed); - } - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_remove_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - if (!ENGINE_remove(ctx->engine)) { - PRINTF_ERR0("engine_remove_nif Leaved: {error, remove_engine_failed}"); - return enif_make_tuple2(env, atom_error, atom_remove_engine_failed); - } - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine, EngineMethod) */ -#ifdef HAS_ENGINE_SUPPORT - struct engine_ctx *ctx; - unsigned int method; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_register_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - // Get Method - if (!enif_get_uint(env, argv[1], &method)) { - PRINTF_ERR0("engine_register_nif Leaved: Parameter Method not an uint"); - return enif_make_badarg(env); - } - - switch(method) - { -#ifdef ENGINE_METHOD_RSA - case ENGINE_METHOD_RSA: - if (!ENGINE_register_RSA(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_DSA - case ENGINE_METHOD_DSA: - if (!ENGINE_register_DSA(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_DH - case ENGINE_METHOD_DH: - if (!ENGINE_register_DH(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_RAND - case ENGINE_METHOD_RAND: - if (!ENGINE_register_RAND(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_ECDH - case ENGINE_METHOD_ECDH: - if (!ENGINE_register_ECDH(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_ECDSA - case ENGINE_METHOD_ECDSA: - if (!ENGINE_register_ECDSA(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_STORE - case ENGINE_METHOD_STORE: - if (!ENGINE_register_STORE(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_CIPHERS - case ENGINE_METHOD_CIPHERS: - if (!ENGINE_register_ciphers(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_DIGESTS - case ENGINE_METHOD_DIGESTS: - if (!ENGINE_register_digests(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_PKEY_METHS - case ENGINE_METHOD_PKEY_METHS: - if (!ENGINE_register_pkey_meths(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_PKEY_ASN1_METHS - case ENGINE_METHOD_PKEY_ASN1_METHS: - if (!ENGINE_register_pkey_asn1_meths(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif -#ifdef ENGINE_METHOD_EC - case ENGINE_METHOD_EC: - if (!ENGINE_register_EC(ctx->engine)) - return enif_make_tuple2(env, atom_error, atom_register_engine_failed); - break; -#endif - default: - return enif_make_tuple2(env, atom_error, atom_engine_method_not_supported); - break; - } - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine, EngineMethod) */ -#ifdef HAS_ENGINE_SUPPORT - struct engine_ctx *ctx; - unsigned int method; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_unregister_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - // Get Method - if (!enif_get_uint(env, argv[1], &method)) { - PRINTF_ERR0("engine_unregister_nif Leaved: Parameter Method not an uint"); - return enif_make_badarg(env); - } - - switch(method) - { -#ifdef ENGINE_METHOD_RSA - case ENGINE_METHOD_RSA: - ENGINE_unregister_RSA(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_DSA - case ENGINE_METHOD_DSA: - ENGINE_unregister_DSA(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_DH - case ENGINE_METHOD_DH: - ENGINE_unregister_DH(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_RAND - case ENGINE_METHOD_RAND: - ENGINE_unregister_RAND(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_ECDH - case ENGINE_METHOD_ECDH: - ENGINE_unregister_ECDH(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_ECDSA - case ENGINE_METHOD_ECDSA: - ENGINE_unregister_ECDSA(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_STORE - case ENGINE_METHOD_STORE: - ENGINE_unregister_STORE(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_CIPHERS - case ENGINE_METHOD_CIPHERS: - ENGINE_unregister_ciphers(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_DIGESTS - case ENGINE_METHOD_DIGESTS: - ENGINE_unregister_digests(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_PKEY_METHS - case ENGINE_METHOD_PKEY_METHS: - ENGINE_unregister_pkey_meths(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_PKEY_ASN1_METHS - case ENGINE_METHOD_PKEY_ASN1_METHS: - ENGINE_unregister_pkey_asn1_meths(ctx->engine); - break; -#endif -#ifdef ENGINE_METHOD_EC - case ENGINE_METHOD_EC: - ENGINE_unregister_EC(ctx->engine); - break; -#endif - default: - break; - } - return atom_ok; -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - ERL_NIF_TERM ret; - ENGINE *engine; - ErlNifBinary engine_bin; - struct engine_ctx *ctx; - - engine = ENGINE_get_first(); - if(!engine) { - enif_alloc_binary(0, &engine_bin); - engine_bin.size = 0; - return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin)); - } - - ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx)); - ctx->engine = engine; - ctx->id = NULL; - - ret = enif_make_resource(env, ctx); - enif_release_resource(ctx); - - return enif_make_tuple2(env, atom_ok, ret); -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - ERL_NIF_TERM ret; - ENGINE *engine; - ErlNifBinary engine_bin; - struct engine_ctx *ctx, *next_ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_get_next_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - engine = ENGINE_get_next(ctx->engine); - if (!engine) { - enif_alloc_binary(0, &engine_bin); - engine_bin.size = 0; - return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin)); - } - - next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx)); - next_ctx->engine = engine; - next_ctx->id = NULL; - - ret = enif_make_resource(env, next_ctx); - enif_release_resource(next_ctx); - - return enif_make_tuple2(env, atom_ok, ret); -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - ErlNifBinary engine_id_bin; - const char *engine_id; - int size; - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - engine_id = ENGINE_get_id(ctx->engine); - if (!engine_id) { - enif_alloc_binary(0, &engine_id_bin); - engine_id_bin.size = 0; - return enif_make_binary(env, &engine_id_bin); - } - - size = strlen(engine_id); - enif_alloc_binary(size, &engine_id_bin); - engine_id_bin.size = size; - memcpy(engine_id_bin.data, engine_id, size); - - return enif_make_binary(env, &engine_id_bin); -#else - return atom_notsup; -#endif -} - -static ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Engine) */ -#ifdef HAS_ENGINE_SUPPORT - ErlNifBinary engine_name_bin; - const char *engine_name; - int size; - struct engine_ctx *ctx; - - // Get Engine - if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { - PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object"); - return enif_make_badarg(env); - } - - engine_name = ENGINE_get_name(ctx->engine); - if (!engine_name) { - enif_alloc_binary(0, &engine_name_bin); - engine_name_bin.size = 0; - return enif_make_binary(env, &engine_name_bin); - } - - size = strlen(engine_name); - enif_alloc_binary(size, &engine_name_bin); - engine_name_bin.size = size; - memcpy(engine_name_bin.data, engine_name, size); - - return enif_make_binary(env, &engine_name_bin); -#else - return atom_notsup; -#endif -} - -#ifdef HAS_ENGINE_SUPPORT -static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i) -{ - ERL_NIF_TERM head, tail; - const ERL_NIF_TERM *tmp_tuple; - ErlNifBinary tmpbin; - int arity; - char* tmpstr; - - if(!enif_is_empty_list(env, term)) { - if(!enif_get_list_cell(env, term, &head, &tail)) { - cmds[i] = NULL; - return -1; - } else { - if(!enif_get_tuple(env, head, &arity, &tmp_tuple) || arity != 2) { - cmds[i] = NULL; - return -1; - } else { - if(!enif_inspect_binary(env, tmp_tuple[0], &tmpbin)) { - cmds[i] = NULL; - return -1; - } else { - tmpstr = enif_alloc(tmpbin.size+1); - (void) memcpy(tmpstr, tmpbin.data, tmpbin.size); - tmpstr[tmpbin.size] = '\0'; - cmds[i++] = tmpstr; - } - if(!enif_inspect_binary(env, tmp_tuple[1], &tmpbin)) { - cmds[i] = NULL; - return -1; - } else { - if(tmpbin.size == 0) - cmds[i++] = NULL; - else { - tmpstr = enif_alloc(tmpbin.size+1); - (void) memcpy(tmpstr, tmpbin.data, tmpbin.size); - tmpstr[tmpbin.size] = '\0'; - cmds[i++] = tmpstr; - } - } - return get_engine_load_cmd_list(env, tail, cmds, i); - } - } - } else { - cmds[i] = NULL; - return 0; - } -} -#endif - -static ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* () */ -#ifdef HAS_ENGINE_SUPPORT - ERL_NIF_TERM method_array[12]; - int i = 0; - -#ifdef ENGINE_METHOD_RSA - method_array[i++] = atom_engine_method_rsa; -#endif -#ifdef ENGINE_METHOD_DSA - method_array[i++] = atom_engine_method_dsa; -#endif -#ifdef ENGINE_METHOD_DH - method_array[i++] = atom_engine_method_dh; -#endif -#ifdef ENGINE_METHOD_RAND - method_array[i++] = atom_engine_method_rand; -#endif -#ifdef ENGINE_METHOD_ECDH - method_array[i++] = atom_engine_method_ecdh; -#endif -#ifdef ENGINE_METHOD_ECDSA - method_array[i++] = atom_engine_method_ecdsa; -#endif -#ifdef ENGINE_METHOD_STORE - method_array[i++] = atom_engine_method_store; -#endif -#ifdef ENGINE_METHOD_CIPHERS - method_array[i++] = atom_engine_method_ciphers; -#endif -#ifdef ENGINE_METHOD_DIGESTS - method_array[i++] = atom_engine_method_digests; -#endif -#ifdef ENGINE_METHOD_PKEY_METHS - method_array[i++] = atom_engine_method_pkey_meths; -#endif -#ifdef ENGINE_METHOD_PKEY_ASN1_METHS - method_array[i++] = atom_engine_method_pkey_asn1_meths; -#endif -#ifdef ENGINE_METHOD_EC - method_array[i++] = atom_engine_method_ec; -#endif - - return enif_make_list_from_array(env, method_array, i); -#else - return atom_notsup; -#endif -} diff --git a/lib/crypto/c_src/dh.c b/lib/crypto/c_src/dh.c new file mode 100644 index 0000000000..0c18ad7a3f --- /dev/null +++ b/lib/crypto/c_src/dh.c @@ -0,0 +1,204 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "dh.h" +#include "bn.h" + +ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (PrivKey|undefined, DHParams=[P,G], Mpint, Len|0) */ + DH *dh_params = NULL; + int mpint; /* 0 or 4 */ + + { + ERL_NIF_TERM head, tail; + BIGNUM + *dh_p = NULL, + *dh_g = NULL, + *priv_key_in = NULL; + unsigned long + len = 0; + + if (!(get_bn_from_bin(env, argv[0], &priv_key_in) + || argv[0] == atom_undefined) + || !enif_get_list_cell(env, argv[1], &head, &tail) + || !get_bn_from_bin(env, head, &dh_p) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dh_g) + || !enif_is_empty_list(env, tail) + || !enif_get_int(env, argv[2], &mpint) || (mpint & ~4) + || !enif_get_ulong(env, argv[3], &len) + + /* Load dh_params with values to use by the generator. + Mem mgmnt transfered from dh_p etc to dh_params */ + || !(dh_params = DH_new()) + || (priv_key_in && !DH_set0_key(dh_params, NULL, priv_key_in)) + || !DH_set0_pqg(dh_params, dh_p, NULL, dh_g) + ) { + if (priv_key_in) BN_free(priv_key_in); + if (dh_p) BN_free(dh_p); + if (dh_g) BN_free(dh_g); + if (dh_params) DH_free(dh_params); + return enif_make_badarg(env); + } + + if (len) { + if (len < BN_num_bits(dh_p)) + DH_set_length(dh_params, len); + else { + if (priv_key_in) BN_free(priv_key_in); + if (dh_p) BN_free(dh_p); + if (dh_g) BN_free(dh_g); + if (dh_params) DH_free(dh_params); + return enif_make_badarg(env); + } + } + } + +#ifdef HAS_EVP_PKEY_CTX + { + EVP_PKEY_CTX *ctx; + EVP_PKEY *dhkey, *params; + int success; + + params = EVP_PKEY_new(); + success = EVP_PKEY_set1_DH(params, dh_params); /* set the key referenced by params to dh_params... */ + DH_free(dh_params); /* ...dh_params (and params) must be freed */ + if (!success) return atom_error; + + ctx = EVP_PKEY_CTX_new(params, NULL); + EVP_PKEY_free(params); + if (!ctx) { + return atom_error; + } + + if (!EVP_PKEY_keygen_init(ctx)) { + /* EVP_PKEY_CTX_free(ctx); */ + return atom_error; + } + + dhkey = EVP_PKEY_new(); + if (!EVP_PKEY_keygen(ctx, &dhkey)) { /* "performs a key generation operation, the ... */ + /*... generated key is written to ppkey." (=last arg) */ + /* EVP_PKEY_CTX_free(ctx); */ + /* EVP_PKEY_free(dhkey); */ + return atom_error; + } + + dh_params = EVP_PKEY_get1_DH(dhkey); /* return the referenced key. dh_params and dhkey must be freed */ + EVP_PKEY_free(dhkey); + if (!dh_params) { + /* EVP_PKEY_CTX_free(ctx); */ + return atom_error; + } + EVP_PKEY_CTX_free(ctx); + } +#else + if (!DH_generate_key(dh_params)) return atom_error; +#endif + { + unsigned char *pub_ptr, *prv_ptr; + int pub_len, prv_len; + ERL_NIF_TERM ret_pub, ret_prv; + const BIGNUM *pub_key_gen, *priv_key_gen; + + DH_get0_key(dh_params, + &pub_key_gen, &priv_key_gen); /* Get pub_key_gen and priv_key_gen. + "The values point to the internal representation of + the public key and private key values. This memory + should not be freed directly." says man */ + pub_len = BN_num_bytes(pub_key_gen); + prv_len = BN_num_bytes(priv_key_gen); + pub_ptr = enif_make_new_binary(env, pub_len+mpint, &ret_pub); + prv_ptr = enif_make_new_binary(env, prv_len+mpint, &ret_prv); + if (mpint) { + put_int32(pub_ptr, pub_len); pub_ptr += 4; + put_int32(prv_ptr, prv_len); prv_ptr += 4; + } + BN_bn2bin(pub_key_gen, pub_ptr); + BN_bn2bin(priv_key_gen, prv_ptr); + ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr, pub_len); + ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr, prv_len); + + DH_free(dh_params); + + return enif_make_tuple2(env, ret_pub, ret_prv); + } +} + +ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */ + BIGNUM *other_pub_key = NULL, + *dh_p = NULL, + *dh_g = NULL; + DH *dh_priv = DH_new(); + + /* Check the arguments and get + my private key (dh_priv), + the peer's public key (other_pub_key), + the parameters p & q + */ + + { + BIGNUM *dummy_pub_key = NULL, + *priv_key = NULL; + ERL_NIF_TERM head, tail; + + if (!get_bn_from_bin(env, argv[0], &other_pub_key) + || !get_bn_from_bin(env, argv[1], &priv_key) + || !enif_get_list_cell(env, argv[2], &head, &tail) + || !get_bn_from_bin(env, head, &dh_p) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dh_g) + || !enif_is_empty_list(env, tail) + + /* Note: DH_set0_key() does not allow setting only the + * private key, although DH_compute_key() does not use the + * public key. Work around this limitation by setting + * the public key to a copy of the private key. + */ + || !(dummy_pub_key = BN_dup(priv_key)) + || !DH_set0_key(dh_priv, dummy_pub_key, priv_key) + || !DH_set0_pqg(dh_priv, dh_p, NULL, dh_g) + ) { + if (dh_p) BN_free(dh_p); + if (dh_g) BN_free(dh_g); + if (other_pub_key) BN_free(other_pub_key); + if (dummy_pub_key) BN_free(dummy_pub_key); + if (priv_key) BN_free(priv_key); + return enif_make_badarg(env); + } + } + { + ErlNifBinary ret_bin; + int size; + + enif_alloc_binary(DH_size(dh_priv), &ret_bin); + size = DH_compute_key(ret_bin.data, other_pub_key, dh_priv); + BN_free(other_pub_key); + DH_free(dh_priv); + if (size<=0) { + enif_release_binary(&ret_bin); + return atom_error; + } + + if (size != ret_bin.size) enif_realloc_binary(&ret_bin, size); + return enif_make_binary(env, &ret_bin); + } +} diff --git a/lib/crypto/c_src/dh.h b/lib/crypto/c_src/dh.h new file mode 100644 index 0000000000..a996b0ea28 --- /dev/null +++ b/lib/crypto/c_src/dh.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_DH_H__ +#define E_DH_H__ 1 + +#include "common.h" + +ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_DH_H__ */ diff --git a/lib/crypto/c_src/digest.c b/lib/crypto/c_src/digest.c new file mode 100644 index 0000000000..9e6199030d --- /dev/null +++ b/lib/crypto/c_src/digest.c @@ -0,0 +1,111 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "digest.h" + +static struct digest_type_t digest_types[] = +{ + {{"md4"}, {&EVP_md4}}, + {{"md5"}, {&EVP_md5}}, + {{"ripemd160"}, {&EVP_ripemd160}}, + {{"sha"}, {&EVP_sha1}}, + {{"sha224"}, +#ifdef HAVE_SHA224 + {&EVP_sha224} +#else + {NULL} +#endif + }, + {{"sha256"}, +#ifdef HAVE_SHA256 + {&EVP_sha256} +#else + {NULL} +#endif + }, + {{"sha384"}, +#ifdef HAVE_SHA384 + {&EVP_sha384} +#else + {NULL} +#endif + }, + {{"sha512"}, +#ifdef HAVE_SHA512 + {&EVP_sha512} +#else + {NULL} +#endif + }, + {{"sha3_224"}, +#ifdef HAVE_SHA3_224 + {&EVP_sha3_224} +#else + {NULL} +#endif + }, + {{"sha3_256"}, +#ifdef HAVE_SHA3_256 + {&EVP_sha3_256} +#else + {NULL} +#endif + }, + {{"sha3_384"}, +#ifdef HAVE_SHA3_384 + {&EVP_sha3_384} +#else + {NULL} +#endif + }, + {{"sha3_512"}, +#ifdef HAVE_SHA3_512 + {&EVP_sha3_512} +#else + {NULL} +#endif + }, + + {{NULL}} +}; + +void init_digest_types(ErlNifEnv* env) +{ + struct digest_type_t* p = digest_types; + + for (p = digest_types; p->type.str; p++) { + p->type.atom = enif_make_atom(env, p->type.str); + if (p->md.funcp) + p->md.p = p->md.funcp(); + } + p->type.atom = atom_false; /* end marker */ +} + +struct digest_type_t* get_digest_type(ERL_NIF_TERM type) +{ + struct digest_type_t* p = NULL; + for (p = digest_types; p->type.atom != atom_false; p++) { + if (type == p->type.atom) { + return p; + } + } + return NULL; +} + diff --git a/lib/crypto/c_src/digest.h b/lib/crypto/c_src/digest.h new file mode 100644 index 0000000000..06852416cf --- /dev/null +++ b/lib/crypto/c_src/digest.h @@ -0,0 +1,40 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_DIGEST_H__ +#define E_DIGEST_H__ 1 + +#include "common.h" + +struct digest_type_t { + union { + const char* str; /* before init, NULL for end-of-table */ + ERL_NIF_TERM atom; /* after init, 'false' for end-of-table */ + }type; + union { + const EVP_MD* (*funcp)(void); /* before init, NULL if notsup */ + const EVP_MD* p; /* after init, NULL if notsup */ + }md; +}; + +void init_digest_types(ErlNifEnv* env); +struct digest_type_t* get_digest_type(ERL_NIF_TERM type); + +#endif /* E_DIGEST_H__ */ diff --git a/lib/crypto/c_src/dss.c b/lib/crypto/c_src/dss.c new file mode 100644 index 0000000000..9d39241382 --- /dev/null +++ b/lib/crypto/c_src/dss.c @@ -0,0 +1,85 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "dss.h" +#include "bn.h" + +int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa) +{ + /* key=[P,Q,G,KEY] */ + ERL_NIF_TERM head, tail; + BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL; + BIGNUM *dummy_pub_key, *priv_key = NULL; + + if (!enif_get_list_cell(env, key, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_p) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_q) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_g) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &priv_key) + || !enif_is_empty_list(env,tail)) { + if (dsa_p) BN_free(dsa_p); + if (dsa_q) BN_free(dsa_q); + if (dsa_g) BN_free(dsa_g); + if (priv_key) BN_free(priv_key); + return 0; + } + + /* Note: DSA_set0_key() does not allow setting only the + * private key, although DSA_sign() does not use the + * public key. Work around this limitation by setting + * the public key to a copy of the private key. + */ + dummy_pub_key = BN_dup(priv_key); + + DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g); + DSA_set0_key(dsa, dummy_pub_key, priv_key); + return 1; +} + + +int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa) +{ + /* key=[P, Q, G, Y] */ + ERL_NIF_TERM head, tail; + BIGNUM *dsa_p = NULL, *dsa_q = NULL, *dsa_g = NULL, *dsa_y = NULL; + + if (!enif_get_list_cell(env, key, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_p) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_q) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_g) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dsa_y) + || !enif_is_empty_list(env,tail)) { + if (dsa_p) BN_free(dsa_p); + if (dsa_q) BN_free(dsa_q); + if (dsa_g) BN_free(dsa_g); + if (dsa_y) BN_free(dsa_y); + return 0; + } + + DSA_set0_pqg(dsa, dsa_p, dsa_q, dsa_g); + DSA_set0_key(dsa, dsa_y, NULL); + return 1; +} diff --git a/lib/crypto/c_src/dss.h b/lib/crypto/c_src/dss.h new file mode 100644 index 0000000000..3275657e98 --- /dev/null +++ b/lib/crypto/c_src/dss.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_DSS_H__ +#define E_DSS_H__ 1 + +#include "common.h" + +int get_dss_private_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa); +int get_dss_public_key(ErlNifEnv* env, ERL_NIF_TERM key, DSA *dsa); + +#endif /* E_DSS_H__ */ diff --git a/lib/crypto/c_src/ec.c b/lib/crypto/c_src/ec.c new file mode 100644 index 0000000000..6d831ec9d2 --- /dev/null +++ b/lib/crypto/c_src/ec.c @@ -0,0 +1,360 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "ec.h" +#include "bn.h" + +#ifdef HAVE_EC +static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg); +static ERL_NIF_TERM point2term(ErlNifEnv* env, + const EC_GROUP *group, + const EC_POINT *point, + point_conversion_form_t form); + +ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env) +{ + ERL_NIF_TERM reason; + if (enif_has_pending_exception(env, &reason)) + return reason; /* dummy return value ignored */ + else + return enif_make_badarg(env); +} + +static EC_KEY* ec_key_new(ErlNifEnv* env, ERL_NIF_TERM curve_arg) +{ + EC_KEY *key = NULL; + int c_arity = -1; + const ERL_NIF_TERM* curve; + ErlNifBinary seed; + BIGNUM *p = NULL; + BIGNUM *a = NULL; + BIGNUM *b = NULL; + BIGNUM *bn_order = NULL; + BIGNUM *cofactor = NULL; + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + + /* {Field, Prime, Point, Order, CoFactor} = Curve */ + if (enif_get_tuple(env,curve_arg,&c_arity,&curve) + && c_arity == 5 + && get_bn_from_bin(env, curve[3], &bn_order) + && (curve[4] != atom_none && get_bn_from_bin(env, curve[4], &cofactor))) { + + int f_arity = -1; + const ERL_NIF_TERM* field; + int p_arity = -1; + const ERL_NIF_TERM* prime; + + long field_bits; + + /* {A, B, Seed} = Prime */ + if (!enif_get_tuple(env,curve[1],&p_arity,&prime) + || !get_bn_from_bin(env, prime[0], &a) + || !get_bn_from_bin(env, prime[1], &b)) + goto out_err; + + if (!enif_get_tuple(env,curve[0],&f_arity,&field)) + goto out_err; + + if (f_arity == 2 && field[0] == atom_prime_field) { + /* {prime_field, Prime} */ + + if (!get_bn_from_bin(env, field[1], &p)) + goto out_err; + + if (BN_is_negative(p) || BN_is_zero(p)) + goto out_err; + + field_bits = BN_num_bits(p); + if (field_bits > OPENSSL_ECC_MAX_FIELD_BITS) + goto out_err; + + /* create the EC_GROUP structure */ + group = EC_GROUP_new_curve_GFp(p, a, b, NULL); + + } else if (f_arity == 3 && field[0] == atom_characteristic_two_field) { +#if defined(OPENSSL_NO_EC2M) + enif_raise_exception(env, atom_notsup); + goto out_err; +#else + /* {characteristic_two_field, M, Basis} */ + + int b_arity = -1; + const ERL_NIF_TERM* basis; + unsigned int k1, k2, k3; + + if ((p = BN_new()) == NULL) + goto out_err; + + if (!enif_get_long(env, field[1], &field_bits) + || field_bits > OPENSSL_ECC_MAX_FIELD_BITS) + goto out_err; + + if (enif_get_tuple(env,field[2],&b_arity,&basis)) { + if (b_arity == 2 + && basis[0] == atom_tpbasis + && enif_get_uint(env, basis[1], &k1)) { + /* {tpbasis, k} = Basis */ + + if (!(field_bits > k1 && k1 > 0)) + goto out_err; + + /* create the polynomial */ + if (!BN_set_bit(p, (int)field_bits) + || !BN_set_bit(p, (int)k1) + || !BN_set_bit(p, 0)) + goto out_err; + + } else if (b_arity == 4 + && basis[0] == atom_ppbasis + && enif_get_uint(env, basis[1], &k1) + && enif_get_uint(env, basis[2], &k2) + && enif_get_uint(env, basis[3], &k3)) { + /* {ppbasis, k1, k2, k3} = Basis */ + + if (!(field_bits > k3 && k3 > k2 && k2 > k1 && k1 > 0)) + goto out_err; + + /* create the polynomial */ + if (!BN_set_bit(p, (int)field_bits) + || !BN_set_bit(p, (int)k1) + || !BN_set_bit(p, (int)k2) + || !BN_set_bit(p, (int)k3) + || !BN_set_bit(p, 0)) + goto out_err; + + } else + goto out_err; + } else if (field[2] == atom_onbasis) { + /* onbasis = Basis */ + /* no parameters */ + goto out_err; + + } else + goto out_err; + + group = EC_GROUP_new_curve_GF2m(p, a, b, NULL); +#endif + } else + goto out_err; + + if (!group) + goto out_err; + + if (enif_inspect_binary(env, prime[2], &seed)) { + EC_GROUP_set_seed(group, seed.data, seed.size); + } + + if (!term2point(env, curve[2], group, &point)) + goto out_err; + + if (BN_is_negative(bn_order) + || BN_is_zero(bn_order) + || BN_num_bits(bn_order) > (int)field_bits + 1) + goto out_err; + + if (!EC_GROUP_set_generator(group, point, bn_order, cofactor)) + goto out_err; + + EC_GROUP_set_asn1_flag(group, 0x0); + + key = EC_KEY_new(); + if (!key) + goto out_err; + EC_KEY_set_group(key, group); + } + else { + goto out_err; + } + + + goto out; + +out_err: + if (key) EC_KEY_free(key); + key = NULL; + +out: + /* some OpenSSL structures are mem-dup'ed into the key, + so we have to free our copies here */ + if (p) BN_free(p); + if (a) BN_free(a); + if (b) BN_free(b); + if (bn_order) BN_free(bn_order); + if (cofactor) BN_free(cofactor); + if (group) EC_GROUP_free(group); + if (point) EC_POINT_free(point); + + return key; +} + +static ERL_NIF_TERM point2term(ErlNifEnv* env, + const EC_GROUP *group, + const EC_POINT *point, + point_conversion_form_t form) +{ + unsigned dlen; + ErlNifBinary bin; + + dlen = EC_POINT_point2oct(group, point, form, NULL, 0, NULL); + if (dlen == 0) + return atom_undefined; + + if (!enif_alloc_binary(dlen, &bin)) + return enif_make_badarg(env); + + if (!EC_POINT_point2oct(group, point, form, bin.data, bin.size, NULL)) { + enif_release_binary(&bin); + return enif_make_badarg(env); + } + ERL_VALGRIND_MAKE_MEM_DEFINED(bin.data, bin.size); + return enif_make_binary(env, &bin); +} + +int term2point(ErlNifEnv* env, ERL_NIF_TERM term, EC_GROUP *group, EC_POINT **pptr) +{ + int ret = 0; + ErlNifBinary bin; + EC_POINT *point; + + if (!enif_inspect_binary(env,term,&bin)) { + return 0; + } + + if ((*pptr = point = EC_POINT_new(group)) == NULL) { + return 0; + } + + /* set the point conversion form */ + EC_GROUP_set_point_conversion_form(group, (point_conversion_form_t)(bin.data[0] & ~0x01)); + + /* extract the ec point */ + if (!EC_POINT_oct2point(group, point, bin.data, bin.size, NULL)) { + EC_POINT_free(point); + *pptr = NULL; + } else + ret = 1; + + return ret; +} + +int get_ec_key(ErlNifEnv* env, + ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub, + EC_KEY** res) +{ + EC_KEY *key = NULL; + BIGNUM *priv_key = NULL; + EC_POINT *pub_key = NULL; + EC_GROUP *group = NULL; + + if (!(priv == atom_undefined || get_bn_from_bin(env, priv, &priv_key)) + || !(pub == atom_undefined || enif_is_binary(env, pub))) { + goto out_err; + } + + key = ec_key_new(env, curve); + + if (!key) { + goto out_err; + } + + if (!group) + group = EC_GROUP_dup(EC_KEY_get0_group(key)); + + if (term2point(env, pub, group, &pub_key)) { + if (!EC_KEY_set_public_key(key, pub_key)) { + goto out_err; + } + } + if (priv != atom_undefined + && !BN_is_zero(priv_key)) { + if (!EC_KEY_set_private_key(key, priv_key)) + goto out_err; + + /* calculate public key (if necessary) */ + if (EC_KEY_get0_public_key(key) == NULL) + { + /* the public key was not included in the SEC1 private + * key => calculate the public key */ + pub_key = EC_POINT_new(group); + if (pub_key == NULL + || !EC_POINT_copy(pub_key, EC_GROUP_get0_generator(group)) + || !EC_POINT_mul(group, pub_key, priv_key, NULL, NULL, NULL) + || !EC_KEY_set_public_key(key, pub_key)) + goto out_err; + } + } + + goto out; + +out_err: + if (key) EC_KEY_free(key); + key = NULL; + +out: + /* some OpenSSL structures are mem-dup'ed into the key, + so we have to free our copies here */ + if (priv_key) BN_clear_free(priv_key); + if (pub_key) EC_POINT_free(pub_key); + if (group) EC_GROUP_free(group); + if (!key) + return 0; + *res = key; + return 1; +} + +#endif /* HAVE_EC */ + +ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ +#if defined(HAVE_EC) + EC_KEY *key = NULL; + const EC_GROUP *group; + const EC_POINT *public_key; + ERL_NIF_TERM priv_key; + ERL_NIF_TERM pub_key = atom_undefined; + + if (!get_ec_key(env, argv[0], argv[1], atom_undefined, &key)) + goto badarg; + + if (argv[1] == atom_undefined) { + if (!EC_KEY_generate_key(key)) + goto badarg; + } + + group = EC_KEY_get0_group(key); + public_key = EC_KEY_get0_public_key(key); + + if (group && public_key) { + pub_key = point2term(env, group, public_key, + EC_KEY_get_conv_form(key)); + } + priv_key = bn2term(env, EC_KEY_get0_private_key(key)); + EC_KEY_free(key); + return enif_make_tuple2(env, pub_key, priv_key); + +badarg: + if (key) + EC_KEY_free(key); + return make_badarg_maybe(env); +#else + return atom_notsup; +#endif +} diff --git a/lib/crypto/c_src/ec.h b/lib/crypto/c_src/ec.h new file mode 100644 index 0000000000..b7e1cc5a46 --- /dev/null +++ b/lib/crypto/c_src/ec.h @@ -0,0 +1,35 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_EC_H__ +#define E_EC_H__ 1 + +#include "common.h" + +#if defined(HAVE_EC) +int get_ec_key(ErlNifEnv* env, ERL_NIF_TERM curve, ERL_NIF_TERM priv, ERL_NIF_TERM pub, + EC_KEY** res); +int term2point(ErlNifEnv* env, ERL_NIF_TERM term, EC_GROUP *group, EC_POINT **pptr); +ERL_NIF_TERM make_badarg_maybe(ErlNifEnv* env); +#endif + +ERL_NIF_TERM ec_key_generate(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_EC_H__ */ diff --git a/lib/crypto/c_src/ecdh.c b/lib/crypto/c_src/ecdh.c new file mode 100644 index 0000000000..d458f3c48e --- /dev/null +++ b/lib/crypto/c_src/ecdh.c @@ -0,0 +1,80 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "ecdh.h" +#include "ec.h" + +/* + (_OthersPublicKey, _MyPrivateKey) + (_OthersPublicKey, _MyEC_Point) +*/ +ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +/* (OtherPublicKey, Curve, My) */ +{ +#if defined(HAVE_EC) + ERL_NIF_TERM ret; + unsigned char *p; + EC_KEY* key = NULL; + int field_size = 0; + int i; + EC_GROUP *group; + const BIGNUM *priv_key; + EC_POINT *my_ecpoint = NULL; + EC_KEY *other_ecdh = NULL; + + if (!get_ec_key(env, argv[1], argv[2], atom_undefined, &key)) + return make_badarg_maybe(env); + + group = EC_GROUP_dup(EC_KEY_get0_group(key)); + priv_key = EC_KEY_get0_private_key(key); + + if (!term2point(env, argv[0], group, &my_ecpoint)) { + goto out_err; + } + + if ((other_ecdh = EC_KEY_new()) == NULL + || !EC_KEY_set_group(other_ecdh, group) + || !EC_KEY_set_private_key(other_ecdh, priv_key)) + goto out_err; + + field_size = EC_GROUP_get_degree(group); + if (field_size <= 0) + goto out_err; + + p = enif_make_new_binary(env, (field_size+7)/8, &ret); + i = ECDH_compute_key(p, (field_size+7)/8, my_ecpoint, other_ecdh, NULL); + + if (i < 0) + goto out_err; +out: + if (group) EC_GROUP_free(group); + if (my_ecpoint) EC_POINT_free(my_ecpoint); + if (other_ecdh) EC_KEY_free(other_ecdh); + if (key) EC_KEY_free(key); + + return ret; + +out_err: + ret = enif_make_badarg(env); + goto out; +#else + return atom_notsup; +#endif +} diff --git a/lib/crypto/c_src/ecdh.h b/lib/crypto/c_src/ecdh.h new file mode 100644 index 0000000000..5ed331e676 --- /dev/null +++ b/lib/crypto/c_src/ecdh.h @@ -0,0 +1,28 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_ECDH_H__ +#define E_ECDH_H__ 1 + +#include "common.h" + +ERL_NIF_TERM ecdh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_ECDH_H__ */ diff --git a/lib/crypto/c_src/eddsa.c b/lib/crypto/c_src/eddsa.c new file mode 100644 index 0000000000..0fdada9677 --- /dev/null +++ b/lib/crypto/c_src/eddsa.c @@ -0,0 +1,51 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "eddsa.h" + +#ifdef HAVE_EDDSA +int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey) +{ + /* key=[K] */ + ERL_NIF_TERM head, tail, tail2, algo; + ErlNifBinary bin; + int type; + + if (!enif_get_list_cell(env, key, &head, &tail) + || !enif_inspect_binary(env, head, &bin) + || !enif_get_list_cell(env, tail, &algo, &tail2) + || !enif_is_empty_list(env, tail2)) { + return 0; + } + if (algo == atom_ed25519) type = EVP_PKEY_ED25519; + else if (algo == atom_ed448) type = EVP_PKEY_ED448; + else + return 0; + + if (public) + *pkey = EVP_PKEY_new_raw_public_key(type, NULL, bin.data, bin.size); + else + *pkey = EVP_PKEY_new_raw_private_key(type, NULL, bin.data, bin.size); + + if (!pkey) + return 0; + return 1; +} +#endif diff --git a/lib/crypto/c_src/eddsa.h b/lib/crypto/c_src/eddsa.h new file mode 100644 index 0000000000..4b30247cab --- /dev/null +++ b/lib/crypto/c_src/eddsa.h @@ -0,0 +1,30 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_EDDSA_H__ +#define E_EDDSA_H__ 1 + +#include "common.h" + +#ifdef HAVE_EDDSA +int get_eddsa_key(ErlNifEnv* env, int public, ERL_NIF_TERM key, EVP_PKEY **pkey); +#endif + +#endif /* E_EDDSA_H__ */ diff --git a/lib/crypto/c_src/engine.c b/lib/crypto/c_src/engine.c new file mode 100644 index 0000000000..dc8e1828ce --- /dev/null +++ b/lib/crypto/c_src/engine.c @@ -0,0 +1,720 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "engine.h" + +#ifdef HAS_ENGINE_SUPPORT +struct engine_ctx { + ENGINE *engine; + char *id; +}; + +static ErlNifResourceType* engine_ctx_rtype; + +static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i); +static int zero_terminate(ErlNifBinary bin, char **buf); + +static void engine_ctx_dtor(ErlNifEnv* env, struct engine_ctx* ctx) { + PRINTF_ERR0("engine_ctx_dtor"); + if(ctx->id) { + PRINTF_ERR1(" non empty ctx->id=%s", ctx->id); + enif_free(ctx->id); + } else + PRINTF_ERR0(" empty ctx->id=NULL"); +} + +int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e) +{ + ERL_NIF_TERM engine_res, key_id_term; + struct engine_ctx *ctx; + ErlNifBinary key_id_bin; + + if (!enif_get_map_value(env, key, atom_engine, &engine_res) || + !enif_get_resource(env, engine_res, engine_ctx_rtype, (void**)&ctx) || + !enif_get_map_value(env, key, atom_key_id, &key_id_term) || + !enif_inspect_binary(env, key_id_term, &key_id_bin)) { + return 0; + } + else { + *e = ctx->engine; + return zero_terminate(key_id_bin, id); + } +} + +char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key) { + ERL_NIF_TERM tmp_term; + ErlNifBinary pwd_bin; + char *pwd = NULL; + if (enif_get_map_value(env, key, atom_password, &tmp_term) && + enif_inspect_binary(env, tmp_term, &pwd_bin) && + zero_terminate(pwd_bin, &pwd) + ) return pwd; + + return NULL; +} + +static int zero_terminate(ErlNifBinary bin, char **buf) { + *buf = enif_alloc(bin.size+1); + if (!*buf) + return 0; + memcpy(*buf, bin.data, bin.size); + *(*buf+bin.size) = 0; + return 1; +} +#endif /* HAS_ENGINE_SUPPORT */ + +int init_engine_ctx(ErlNifEnv *env) { +#ifdef HAS_ENGINE_SUPPORT + engine_ctx_rtype = enif_open_resource_type(env, NULL, "ENGINE_CTX", + (ErlNifResourceDtor*) engine_ctx_dtor, + ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, + NULL); + if (engine_ctx_rtype == NULL) { + PRINTF_ERR0("CRYPTO: Could not open resource type 'ENGINE_CTX'"); + return 0; + } +#endif + + return 1; +} + +ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (EngineId) */ +#ifdef HAS_ENGINE_SUPPORT + ERL_NIF_TERM ret; + ErlNifBinary engine_id_bin; + char *engine_id; + ENGINE *engine; + struct engine_ctx *ctx; + + // Get Engine Id + if(!enif_inspect_binary(env, argv[0], &engine_id_bin)) { + PRINTF_ERR0("engine_by_id_nif Leaved: badarg"); + return enif_make_badarg(env); + } else { + engine_id = enif_alloc(engine_id_bin.size+1); + (void) memcpy(engine_id, engine_id_bin.data, engine_id_bin.size); + engine_id[engine_id_bin.size] = '\0'; + } + + engine = ENGINE_by_id(engine_id); + if(!engine) { + enif_free(engine_id); + PRINTF_ERR0("engine_by_id_nif Leaved: {error, bad_engine_id}"); + return enif_make_tuple2(env, atom_error, atom_bad_engine_id); + } + + ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx)); + ctx->engine = engine; + ctx->id = engine_id; + + ret = enif_make_resource(env, ctx); + enif_release_resource(ctx); + + return enif_make_tuple2(env, atom_ok, ret); +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + ERL_NIF_TERM ret = atom_ok; + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_init_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + if (!ENGINE_init(ctx->engine)) { + //ERR_print_errors_fp(stderr); + PRINTF_ERR0("engine_init_nif Leaved: {error, engine_init_failed}"); + return enif_make_tuple2(env, atom_error, atom_engine_init_failed); + } + + return ret; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_free_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + ENGINE_free(ctx->engine); + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_finish_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + ENGINE_finish(ctx->engine); + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* () */ +#ifdef HAS_ENGINE_SUPPORT + ENGINE_load_dynamic(); + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine, Commands) */ +#ifdef HAS_ENGINE_SUPPORT + ERL_NIF_TERM ret = atom_ok; + unsigned int cmds_len = 0; + char **cmds = NULL; + struct engine_ctx *ctx; + int i, optional = 0; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + PRINTF_ERR1("Engine Id: %s\r\n", ENGINE_get_id(ctx->engine)); + + // Get Command List + if(!enif_get_list_length(env, argv[1], &cmds_len)) { + PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Bad Command List"); + return enif_make_badarg(env); + } else { + cmds_len *= 2; // Key-Value list from erlang + cmds = enif_alloc((cmds_len+1)*sizeof(char*)); + if(get_engine_load_cmd_list(env, argv[1], cmds, 0)) { + PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Couldn't read Command List"); + ret = enif_make_badarg(env); + goto error; + } + } + + if(!enif_get_int(env, argv[2], &optional)) { + PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: Parameter optional not an integer"); + return enif_make_badarg(env); + } + + for(i = 0; i < cmds_len; i+=2) { + PRINTF_ERR2("Cmd: %s:%s\r\n", + cmds[i] ? cmds[i] : "(NULL)", + cmds[i+1] ? cmds[i+1] : "(NULL)"); + if(!ENGINE_ctrl_cmd_string(ctx->engine, cmds[i], cmds[i+1], optional)) { + PRINTF_ERR2("Command failed: %s:%s\r\n", + cmds[i] ? cmds[i] : "(NULL)", + cmds[i+1] ? cmds[i+1] : "(NULL)"); + //ENGINE_free(ctx->engine); + ret = enif_make_tuple2(env, atom_error, atom_ctrl_cmd_failed); + PRINTF_ERR0("engine_ctrl_cmd_strings_nif Leaved: {error, ctrl_cmd_failed}"); + goto error; + } + } + + error: + for(i = 0; cmds != NULL && cmds[i] != NULL; i++) + enif_free(cmds[i]); + enif_free(cmds); + return ret; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_add_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + if (!ENGINE_add(ctx->engine)) { + PRINTF_ERR0("engine_add_nif Leaved: {error, add_engine_failed}"); + return enif_make_tuple2(env, atom_error, atom_add_engine_failed); + } + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_remove_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + if (!ENGINE_remove(ctx->engine)) { + PRINTF_ERR0("engine_remove_nif Leaved: {error, remove_engine_failed}"); + return enif_make_tuple2(env, atom_error, atom_remove_engine_failed); + } + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine, EngineMethod) */ +#ifdef HAS_ENGINE_SUPPORT + struct engine_ctx *ctx; + unsigned int method; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_register_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + // Get Method + if (!enif_get_uint(env, argv[1], &method)) { + PRINTF_ERR0("engine_register_nif Leaved: Parameter Method not an uint"); + return enif_make_badarg(env); + } + + switch(method) + { +#ifdef ENGINE_METHOD_RSA + case ENGINE_METHOD_RSA: + if (!ENGINE_register_RSA(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_DSA + case ENGINE_METHOD_DSA: + if (!ENGINE_register_DSA(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_DH + case ENGINE_METHOD_DH: + if (!ENGINE_register_DH(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_RAND + case ENGINE_METHOD_RAND: + if (!ENGINE_register_RAND(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_ECDH + case ENGINE_METHOD_ECDH: + if (!ENGINE_register_ECDH(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_ECDSA + case ENGINE_METHOD_ECDSA: + if (!ENGINE_register_ECDSA(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_STORE + case ENGINE_METHOD_STORE: + if (!ENGINE_register_STORE(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_CIPHERS + case ENGINE_METHOD_CIPHERS: + if (!ENGINE_register_ciphers(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_DIGESTS + case ENGINE_METHOD_DIGESTS: + if (!ENGINE_register_digests(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_PKEY_METHS + case ENGINE_METHOD_PKEY_METHS: + if (!ENGINE_register_pkey_meths(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_PKEY_ASN1_METHS + case ENGINE_METHOD_PKEY_ASN1_METHS: + if (!ENGINE_register_pkey_asn1_meths(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif +#ifdef ENGINE_METHOD_EC + case ENGINE_METHOD_EC: + if (!ENGINE_register_EC(ctx->engine)) + return enif_make_tuple2(env, atom_error, atom_register_engine_failed); + break; +#endif + default: + return enif_make_tuple2(env, atom_error, atom_engine_method_not_supported); + break; + } + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine, EngineMethod) */ +#ifdef HAS_ENGINE_SUPPORT + struct engine_ctx *ctx; + unsigned int method; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_unregister_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + // Get Method + if (!enif_get_uint(env, argv[1], &method)) { + PRINTF_ERR0("engine_unregister_nif Leaved: Parameter Method not an uint"); + return enif_make_badarg(env); + } + + switch(method) + { +#ifdef ENGINE_METHOD_RSA + case ENGINE_METHOD_RSA: + ENGINE_unregister_RSA(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_DSA + case ENGINE_METHOD_DSA: + ENGINE_unregister_DSA(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_DH + case ENGINE_METHOD_DH: + ENGINE_unregister_DH(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_RAND + case ENGINE_METHOD_RAND: + ENGINE_unregister_RAND(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_ECDH + case ENGINE_METHOD_ECDH: + ENGINE_unregister_ECDH(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_ECDSA + case ENGINE_METHOD_ECDSA: + ENGINE_unregister_ECDSA(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_STORE + case ENGINE_METHOD_STORE: + ENGINE_unregister_STORE(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_CIPHERS + case ENGINE_METHOD_CIPHERS: + ENGINE_unregister_ciphers(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_DIGESTS + case ENGINE_METHOD_DIGESTS: + ENGINE_unregister_digests(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_PKEY_METHS + case ENGINE_METHOD_PKEY_METHS: + ENGINE_unregister_pkey_meths(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_PKEY_ASN1_METHS + case ENGINE_METHOD_PKEY_ASN1_METHS: + ENGINE_unregister_pkey_asn1_meths(ctx->engine); + break; +#endif +#ifdef ENGINE_METHOD_EC + case ENGINE_METHOD_EC: + ENGINE_unregister_EC(ctx->engine); + break; +#endif + default: + break; + } + return atom_ok; +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + ERL_NIF_TERM ret; + ENGINE *engine; + ErlNifBinary engine_bin; + struct engine_ctx *ctx; + + engine = ENGINE_get_first(); + if(!engine) { + enif_alloc_binary(0, &engine_bin); + engine_bin.size = 0; + return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin)); + } + + ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx)); + ctx->engine = engine; + ctx->id = NULL; + + ret = enif_make_resource(env, ctx); + enif_release_resource(ctx); + + return enif_make_tuple2(env, atom_ok, ret); +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + ERL_NIF_TERM ret; + ENGINE *engine; + ErlNifBinary engine_bin; + struct engine_ctx *ctx, *next_ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_get_next_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + engine = ENGINE_get_next(ctx->engine); + if (!engine) { + enif_alloc_binary(0, &engine_bin); + engine_bin.size = 0; + return enif_make_tuple2(env, atom_ok, enif_make_binary(env, &engine_bin)); + } + + next_ctx = enif_alloc_resource(engine_ctx_rtype, sizeof(struct engine_ctx)); + next_ctx->engine = engine; + next_ctx->id = NULL; + + ret = enif_make_resource(env, next_ctx); + enif_release_resource(next_ctx); + + return enif_make_tuple2(env, atom_ok, ret); +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + ErlNifBinary engine_id_bin; + const char *engine_id; + int size; + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + engine_id = ENGINE_get_id(ctx->engine); + if (!engine_id) { + enif_alloc_binary(0, &engine_id_bin); + engine_id_bin.size = 0; + return enif_make_binary(env, &engine_id_bin); + } + + size = strlen(engine_id); + enif_alloc_binary(size, &engine_id_bin); + engine_id_bin.size = size; + memcpy(engine_id_bin.data, engine_id, size); + + return enif_make_binary(env, &engine_id_bin); +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Engine) */ +#ifdef HAS_ENGINE_SUPPORT + ErlNifBinary engine_name_bin; + const char *engine_name; + int size; + struct engine_ctx *ctx; + + // Get Engine + if (!enif_get_resource(env, argv[0], engine_ctx_rtype, (void**)&ctx)) { + PRINTF_ERR0("engine_get_id_nif Leaved: Parameter not an engine resource object"); + return enif_make_badarg(env); + } + + engine_name = ENGINE_get_name(ctx->engine); + if (!engine_name) { + enif_alloc_binary(0, &engine_name_bin); + engine_name_bin.size = 0; + return enif_make_binary(env, &engine_name_bin); + } + + size = strlen(engine_name); + enif_alloc_binary(size, &engine_name_bin); + engine_name_bin.size = size; + memcpy(engine_name_bin.data, engine_name, size); + + return enif_make_binary(env, &engine_name_bin); +#else + return atom_notsup; +#endif +} + +#ifdef HAS_ENGINE_SUPPORT +static int get_engine_load_cmd_list(ErlNifEnv* env, const ERL_NIF_TERM term, char **cmds, int i) +{ + ERL_NIF_TERM head, tail; + const ERL_NIF_TERM *tmp_tuple; + ErlNifBinary tmpbin; + int arity; + char* tmpstr; + + if(!enif_is_empty_list(env, term)) { + if(!enif_get_list_cell(env, term, &head, &tail)) { + cmds[i] = NULL; + return -1; + } else { + if(!enif_get_tuple(env, head, &arity, &tmp_tuple) || arity != 2) { + cmds[i] = NULL; + return -1; + } else { + if(!enif_inspect_binary(env, tmp_tuple[0], &tmpbin)) { + cmds[i] = NULL; + return -1; + } else { + tmpstr = enif_alloc(tmpbin.size+1); + (void) memcpy(tmpstr, tmpbin.data, tmpbin.size); + tmpstr[tmpbin.size] = '\0'; + cmds[i++] = tmpstr; + } + if(!enif_inspect_binary(env, tmp_tuple[1], &tmpbin)) { + cmds[i] = NULL; + return -1; + } else { + if(tmpbin.size == 0) + cmds[i++] = NULL; + else { + tmpstr = enif_alloc(tmpbin.size+1); + (void) memcpy(tmpstr, tmpbin.data, tmpbin.size); + tmpstr[tmpbin.size] = '\0'; + cmds[i++] = tmpstr; + } + } + return get_engine_load_cmd_list(env, tail, cmds, i); + } + } + } else { + cmds[i] = NULL; + return 0; + } +} +#endif /* HAS_ENGINE_SUPPORT */ + +ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* () */ +#ifdef HAS_ENGINE_SUPPORT + ERL_NIF_TERM method_array[12]; + int i = 0; + +#ifdef ENGINE_METHOD_RSA + method_array[i++] = atom_engine_method_rsa; +#endif +#ifdef ENGINE_METHOD_DSA + method_array[i++] = atom_engine_method_dsa; +#endif +#ifdef ENGINE_METHOD_DH + method_array[i++] = atom_engine_method_dh; +#endif +#ifdef ENGINE_METHOD_RAND + method_array[i++] = atom_engine_method_rand; +#endif +#ifdef ENGINE_METHOD_ECDH + method_array[i++] = atom_engine_method_ecdh; +#endif +#ifdef ENGINE_METHOD_ECDSA + method_array[i++] = atom_engine_method_ecdsa; +#endif +#ifdef ENGINE_METHOD_STORE + method_array[i++] = atom_engine_method_store; +#endif +#ifdef ENGINE_METHOD_CIPHERS + method_array[i++] = atom_engine_method_ciphers; +#endif +#ifdef ENGINE_METHOD_DIGESTS + method_array[i++] = atom_engine_method_digests; +#endif +#ifdef ENGINE_METHOD_PKEY_METHS + method_array[i++] = atom_engine_method_pkey_meths; +#endif +#ifdef ENGINE_METHOD_PKEY_ASN1_METHS + method_array[i++] = atom_engine_method_pkey_asn1_meths; +#endif +#ifdef ENGINE_METHOD_EC + method_array[i++] = atom_engine_method_ec; +#endif + + return enif_make_list_from_array(env, method_array, i); +#else + return atom_notsup; +#endif +} diff --git a/lib/crypto/c_src/engine.h b/lib/crypto/c_src/engine.h new file mode 100644 index 0000000000..4a2eed9672 --- /dev/null +++ b/lib/crypto/c_src/engine.h @@ -0,0 +1,49 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_ENGINE_H__ +#define E_ENGINE_H__ 1 + +#include "common.h" + +#ifdef HAS_ENGINE_SUPPORT +int get_engine_and_key_id(ErlNifEnv *env, ERL_NIF_TERM key, char ** id, ENGINE **e); +char *get_key_password(ErlNifEnv *env, ERL_NIF_TERM key); +#endif /* HAS_ENGINE_SUPPORT */ + +int init_engine_ctx(ErlNifEnv *env); + +ERL_NIF_TERM engine_by_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_finish_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_free_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_load_dynamic_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_ctrl_cmd_strings_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_register_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_unregister_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_add_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_remove_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_get_first_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_get_next_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_get_id_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_get_name_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM engine_get_all_methods_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_ENGINE_H__ */ diff --git a/lib/crypto/c_src/evp.c b/lib/crypto/c_src/evp.c new file mode 100644 index 0000000000..3c55ab630b --- /dev/null +++ b/lib/crypto/c_src/evp.c @@ -0,0 +1,124 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "evp.h" + +ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) + /* (Curve, PeerBin, MyBin) */ +{ +#ifdef HAVE_ED_CURVE_DH + int type; + EVP_PKEY_CTX *ctx = NULL; + ErlNifBinary peer_bin, my_bin, key_bin; + EVP_PKEY *peer_key = NULL, *my_key = NULL; + size_t max_size; + + if (argv[0] == atom_x25519) type = EVP_PKEY_X25519; + else if (argv[0] == atom_x448) type = EVP_PKEY_X448; + else return enif_make_badarg(env); + + if (!enif_inspect_binary(env, argv[1], &peer_bin) || + !enif_inspect_binary(env, argv[2], &my_bin)) + goto return_badarg; + + if (!(my_key = EVP_PKEY_new_raw_private_key(type, NULL, my_bin.data, my_bin.size)) || + !(ctx = EVP_PKEY_CTX_new(my_key, NULL))) + goto return_badarg; + + if (!EVP_PKEY_derive_init(ctx)) + goto return_badarg; + + if (!(peer_key = EVP_PKEY_new_raw_public_key(type, NULL, peer_bin.data, peer_bin.size)) || + !EVP_PKEY_derive_set_peer(ctx, peer_key)) + goto return_badarg; + + if (!EVP_PKEY_derive(ctx, NULL, &max_size)) + goto return_badarg; + + if (!enif_alloc_binary(max_size, &key_bin) || + !EVP_PKEY_derive(ctx, key_bin.data, &key_bin.size)) + goto return_badarg; + + if (key_bin.size < max_size) { + size_t actual_size = key_bin.size; + if (!enif_realloc_binary(&key_bin, actual_size)) + goto return_badarg; + } + + EVP_PKEY_free(my_key); + EVP_PKEY_free(peer_key); + EVP_PKEY_CTX_free(ctx); + return enif_make_binary(env, &key_bin); + +return_badarg: + if (my_key) EVP_PKEY_free(my_key); + if (peer_key) EVP_PKEY_free(peer_key); + if (ctx) EVP_PKEY_CTX_free(ctx); + return enif_make_badarg(env); +#else + return atom_notsup; +#endif +} + +ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +/* (Curve) */ +{ +#ifdef HAVE_ED_CURVE_DH + int type; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + ERL_NIF_TERM ret_pub, ret_prv; + size_t key_len; + + if (argv[0] == atom_x25519) type = EVP_PKEY_X25519; + else if (argv[0] == atom_x448) type = EVP_PKEY_X448; + else return enif_make_badarg(env); + + if (!(ctx = EVP_PKEY_CTX_new_id(type, NULL))) return enif_make_badarg(env); + + if (!EVP_PKEY_keygen_init(ctx)) goto return_error; + if (!EVP_PKEY_keygen(ctx, &pkey)) goto return_error; + + if (!EVP_PKEY_get_raw_public_key(pkey, NULL, &key_len)) goto return_error; + if (!EVP_PKEY_get_raw_public_key(pkey, + enif_make_new_binary(env, key_len, &ret_pub), + &key_len)) + goto return_error; + + if (!EVP_PKEY_get_raw_private_key(pkey, NULL, &key_len)) goto return_error; + if (!EVP_PKEY_get_raw_private_key(pkey, + enif_make_new_binary(env, key_len, &ret_prv), + &key_len)) + goto return_error; + + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return enif_make_tuple2(env, ret_pub, ret_prv); + +return_error: + if (pkey) EVP_PKEY_free(pkey); + if (ctx) EVP_PKEY_CTX_free(ctx); + return atom_error; + +#else + return atom_notsup; +#endif +} + diff --git a/lib/crypto/c_src/evp.h b/lib/crypto/c_src/evp.h new file mode 100644 index 0000000000..d767260262 --- /dev/null +++ b/lib/crypto/c_src/evp.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_EVP_H__ +#define E_EVP_H__ 1 + +#include "common.h" + +ERL_NIF_TERM evp_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM evp_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_EVP_H__ */ diff --git a/lib/crypto/c_src/evp_compat.h b/lib/crypto/c_src/evp_compat.h new file mode 100644 index 0000000000..98c861c45e --- /dev/null +++ b/lib/crypto/c_src/evp_compat.h @@ -0,0 +1,196 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_EVP_COMPAT_H__ +#define E_EVP_COMPAT_H__ 1 + +/* + * In OpenSSL 1.1.0, most structs are opaque. That means that + * the structs cannot be allocated as automatic variables on the + * C stack (because the size is unknown) and that it is necessary + * to use access functions. + * + * For backward compatibility to previous versions of OpenSSL, define + * on our versions of the new functions defined in 1.1.0 here, so that + * we don't have to sprinkle ifdefs throughout the code. + */ + +static INLINE HMAC_CTX *HMAC_CTX_new(void); +static INLINE void HMAC_CTX_free(HMAC_CTX *ctx); + +static INLINE HMAC_CTX *HMAC_CTX_new() +{ + HMAC_CTX *ctx = CRYPTO_malloc(sizeof(HMAC_CTX), __FILE__, __LINE__); + HMAC_CTX_init(ctx); + return ctx; +} + +static INLINE void HMAC_CTX_free(HMAC_CTX *ctx) +{ + HMAC_CTX_cleanup(ctx); + CRYPTO_free(ctx); +} + +#define EVP_MD_CTX_new() EVP_MD_CTX_create() +#define EVP_MD_CTX_free(ctx) EVP_MD_CTX_destroy(ctx) + +static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb); + +static INLINE void *BN_GENCB_get_arg(BN_GENCB *cb) +{ + return cb->arg; +} + +static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); +static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d); +static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q); +static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q); +static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp); +static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp); + +static INLINE int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + r->n = n; + r->e = e; + r->d = d; + return 1; +} + +static INLINE void RSA_get0_key(const RSA *r, const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) +{ + *n = r->n; + *e = r->e; + *d = r->d; +} + +static INLINE int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) +{ + r->p = p; + r->q = q; + return 1; +} + +static INLINE void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) +{ + *p = r->p; + *q = r->q; +} + +static INLINE int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) +{ + r->dmp1 = dmp1; + r->dmq1 = dmq1; + r->iqmp = iqmp; + return 1; +} + +static INLINE void RSA_get0_crt_params(const RSA *r, const BIGNUM **dmp1, const BIGNUM **dmq1, const BIGNUM **iqmp) +{ + *dmp1 = r->dmp1; + *dmq1 = r->dmq1; + *iqmp = r->iqmp; +} + +static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key); +static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g); +static INLINE void DSA_get0_pqg(const DSA *dsa, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); +static INLINE void DSA_get0_key(const DSA *dsa, + const BIGNUM **pub_key, const BIGNUM **priv_key); + +static INLINE int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) +{ + d->pub_key = pub_key; + d->priv_key = priv_key; + return 1; +} + +static INLINE int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + d->p = p; + d->q = q; + d->g = g; + return 1; +} + +static INLINE void +DSA_get0_pqg(const DSA *dsa, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + *p = dsa->p; + *q = dsa->q; + *g = dsa->g; +} + +static INLINE void +DSA_get0_key(const DSA *dsa, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + if (pub_key) *pub_key = dsa->pub_key; + if (priv_key) *priv_key = dsa->priv_key; +} + + + +static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key); +static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g); +static INLINE int DH_set_length(DH *dh, long length); +static INLINE void DH_get0_pqg(const DH *dh, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g); +static INLINE void DH_get0_key(const DH *dh, + const BIGNUM **pub_key, const BIGNUM **priv_key); + +static INLINE int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) +{ + dh->pub_key = pub_key; + dh->priv_key = priv_key; + return 1; +} + +static INLINE int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + dh->p = p; + dh->q = q; + dh->g = g; + return 1; +} + +static INLINE int DH_set_length(DH *dh, long length) +{ + dh->length = length; + return 1; +} + + + +static INLINE void +DH_get0_pqg(const DH *dh, const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + *p = dh->p; + *q = dh->q; + *g = dh->g; +} + +static INLINE void +DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + if (pub_key) *pub_key = dh->pub_key; + if (priv_key) *priv_key = dh->priv_key; +} + +#endif /* E_EVP_COMPAT_H__ */ diff --git a/lib/crypto/c_src/fips.c b/lib/crypto/c_src/fips.c new file mode 100644 index 0000000000..b2d892d00b --- /dev/null +++ b/lib/crypto/c_src/fips.c @@ -0,0 +1,52 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "fips.h" + +ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ +#ifdef FIPS_SUPPORT + return FIPS_mode() ? atom_enabled : atom_not_enabled; +#else + return atom_not_supported; +#endif +} + +ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Boolean) */ + if (argv[0] == atom_true) { +#ifdef FIPS_SUPPORT + if (FIPS_mode_set(1)) { + return atom_true; + } +#endif + PRINTF_ERR0("CRYPTO: Could not setup FIPS mode"); + return atom_false; + } else if (argv[0] == atom_false) { +#ifdef FIPS_SUPPORT + if (!FIPS_mode_set(0)) { + return atom_false; + } +#endif + return atom_true; + } else { + return enif_make_badarg(env); + } +} diff --git a/lib/crypto/c_src/fips.h b/lib/crypto/c_src/fips.h new file mode 100644 index 0000000000..9a436bd202 --- /dev/null +++ b/lib/crypto/c_src/fips.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_FIPS_H__ +#define E_FIPS_H__ 1 + +#include "common.h" + +ERL_NIF_TERM info_fips(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM enable_fips_mode(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_FIPS_H__ */ diff --git a/lib/crypto/c_src/hash.c b/lib/crypto/c_src/hash.c new file mode 100644 index 0000000000..52748dc933 --- /dev/null +++ b/lib/crypto/c_src/hash.c @@ -0,0 +1,405 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "hash.h" +#include "digest.h" + +#define MD5_CTX_LEN (sizeof(MD5_CTX)) +#define MD4_CTX_LEN (sizeof(MD4_CTX)) +#define RIPEMD160_CTX_LEN (sizeof(RIPEMD160_CTX)) + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) +struct evp_md_ctx { + EVP_MD_CTX* ctx; +}; + +/* Define resource types for OpenSSL context structures. */ +static ErlNifResourceType* evp_md_ctx_rtype; + +static void evp_md_ctx_dtor(ErlNifEnv* env, struct evp_md_ctx *ctx) { + EVP_MD_CTX_free(ctx->ctx); +} +#endif + +int init_hash_ctx(ErlNifEnv* env) { +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) + evp_md_ctx_rtype = enif_open_resource_type(env, NULL, "EVP_MD_CTX", + (ErlNifResourceDtor*) evp_md_ctx_dtor, + ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, + NULL); + if (evp_md_ctx_rtype == NULL) { + PRINTF_ERR0("CRYPTO: Could not open resource type 'EVP_MD_CTX'"); + return 0; + } +#endif + + return 1; +} + +ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type, Data) */ + struct digest_type_t *digp = NULL; + const EVP_MD *md; + ErlNifBinary data; + ERL_NIF_TERM ret; + unsigned ret_size; + + digp = get_digest_type(argv[0]); + if (!digp || + !enif_inspect_iolist_as_binary(env, argv[1], &data)) { + return enif_make_badarg(env); + } + md = digp->md.p; + if (!md) { + return atom_notsup; + } + + ret_size = (unsigned)EVP_MD_size(md); + ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE); + if (!EVP_Digest(data.data, data.size, + enif_make_new_binary(env, ret_size, &ret), &ret_size, + md, NULL)) { + return atom_notsup; + } + ASSERT(ret_size == (unsigned)EVP_MD_size(md)); + + CONSUME_REDS(env, data); + return ret; +} + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) + +ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type) */ + struct digest_type_t *digp = NULL; + struct evp_md_ctx *ctx; + ERL_NIF_TERM ret; + + digp = get_digest_type(argv[0]); + if (!digp) { + return enif_make_badarg(env); + } + if (!digp->md.p) { + return atom_notsup; + } + + ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx)); + ctx->ctx = EVP_MD_CTX_new(); + if (!EVP_DigestInit(ctx->ctx, digp->md.p)) { + enif_release_resource(ctx); + return atom_notsup; + } + ret = enif_make_resource(env, ctx); + enif_release_resource(ctx); + return ret; +} + +ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Context, Data) */ + struct evp_md_ctx *ctx, *new_ctx; + ErlNifBinary data; + ERL_NIF_TERM ret; + + if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx) || + !enif_inspect_iolist_as_binary(env, argv[1], &data)) { + return enif_make_badarg(env); + } + + new_ctx = enif_alloc_resource(evp_md_ctx_rtype, sizeof(struct evp_md_ctx)); + new_ctx->ctx = EVP_MD_CTX_new(); + if (!EVP_MD_CTX_copy(new_ctx->ctx, ctx->ctx) || + !EVP_DigestUpdate(new_ctx->ctx, data.data, data.size)) { + enif_release_resource(new_ctx); + return atom_notsup; + } + + ret = enif_make_resource(env, new_ctx); + enif_release_resource(new_ctx); + CONSUME_REDS(env, data); + return ret; +} + +ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Context) */ + struct evp_md_ctx *ctx; + EVP_MD_CTX *new_ctx; + ERL_NIF_TERM ret; + unsigned ret_size; + + if (!enif_get_resource(env, argv[0], evp_md_ctx_rtype, (void**)&ctx)) { + return enif_make_badarg(env); + } + + ret_size = (unsigned)EVP_MD_CTX_size(ctx->ctx); + ASSERT(0 < ret_size && ret_size <= EVP_MAX_MD_SIZE); + + new_ctx = EVP_MD_CTX_new(); + if (!EVP_MD_CTX_copy(new_ctx, ctx->ctx) || + !EVP_DigestFinal(new_ctx, + enif_make_new_binary(env, ret_size, &ret), + &ret_size)) { + EVP_MD_CTX_free(new_ctx); + return atom_notsup; + } + EVP_MD_CTX_free(new_ctx); + ASSERT(ret_size == (unsigned)EVP_MD_CTX_size(ctx->ctx)); + + return ret; +} + +#else /* if OPENSSL_VERSION_NUMBER < 1.0 */ + +ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type) */ + typedef int (*init_fun)(unsigned char*); + struct digest_type_t *digp = NULL; + ERL_NIF_TERM ctx; + size_t ctx_size = 0; + init_fun ctx_init = 0; + + digp = get_digest_type(argv[0]); + if (!digp) { + return enif_make_badarg(env); + } + if (!digp->md.p) { + return atom_notsup; + } + + switch (EVP_MD_type(digp->md.p)) + { + case NID_md4: + ctx_size = MD4_CTX_LEN; + ctx_init = (init_fun)(&MD4_Init); + break; + case NID_md5: + ctx_size = MD5_CTX_LEN; + ctx_init = (init_fun)(&MD5_Init); + break; + case NID_ripemd160: + ctx_size = RIPEMD160_CTX_LEN; + ctx_init = (init_fun)(&RIPEMD160_Init); + break; + case NID_sha1: + ctx_size = sizeof(SHA_CTX); + ctx_init = (init_fun)(&SHA1_Init); + break; +#ifdef HAVE_SHA224 + case NID_sha224: + ctx_size = sizeof(SHA256_CTX); + ctx_init = (init_fun)(&SHA224_Init); + break; +#endif +#ifdef HAVE_SHA256 + case NID_sha256: + ctx_size = sizeof(SHA256_CTX); + ctx_init = (init_fun)(&SHA256_Init); + break; +#endif +#ifdef HAVE_SHA384 + case NID_sha384: + ctx_size = sizeof(SHA512_CTX); + ctx_init = (init_fun)(&SHA384_Init); + break; +#endif +#ifdef HAVE_SHA512 + case NID_sha512: + ctx_size = sizeof(SHA512_CTX); + ctx_init = (init_fun)(&SHA512_Init); + break; +#endif + default: + return atom_notsup; + } + ASSERT(ctx_size); + ASSERT(ctx_init); + + ctx_init(enif_make_new_binary(env, ctx_size, &ctx)); + return enif_make_tuple2(env, argv[0], ctx); +} + +ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* ({Type, Context}, Data) */ + typedef int (*update_fun)(unsigned char*, const unsigned char*, size_t); + ERL_NIF_TERM new_ctx; + ErlNifBinary ctx, data; + const ERL_NIF_TERM *tuple; + int arity; + struct digest_type_t *digp = NULL; + unsigned char *ctx_buff; + size_t ctx_size = 0; + update_fun ctx_update = 0; + + if (!enif_get_tuple(env, argv[0], &arity, &tuple) || + arity != 2 || + !(digp = get_digest_type(tuple[0])) || + !enif_inspect_binary(env, tuple[1], &ctx) || + !enif_inspect_iolist_as_binary(env, argv[1], &data)) { + return enif_make_badarg(env); + } + if (!digp->md.p) { + return atom_notsup; + } + + switch (EVP_MD_type(digp->md.p)) + { + case NID_md4: + ctx_size = MD4_CTX_LEN; + ctx_update = (update_fun)(&MD4_Update); + break; + case NID_md5: + ctx_size = MD5_CTX_LEN; + ctx_update = (update_fun)(&MD5_Update); + break; + case NID_ripemd160: + ctx_size = RIPEMD160_CTX_LEN; + ctx_update = (update_fun)(&RIPEMD160_Update); + break; + case NID_sha1: + ctx_size = sizeof(SHA_CTX); + ctx_update = (update_fun)(&SHA1_Update); + break; +#ifdef HAVE_SHA224 + case NID_sha224: + ctx_size = sizeof(SHA256_CTX); + ctx_update = (update_fun)(&SHA224_Update); + break; +#endif +#ifdef HAVE_SHA256 + case NID_sha256: + ctx_size = sizeof(SHA256_CTX); + ctx_update = (update_fun)(&SHA256_Update); + break; +#endif +#ifdef HAVE_SHA384 + case NID_sha384: + ctx_size = sizeof(SHA512_CTX); + ctx_update = (update_fun)(&SHA384_Update); + break; +#endif +#ifdef HAVE_SHA512 + case NID_sha512: + ctx_size = sizeof(SHA512_CTX); + ctx_update = (update_fun)(&SHA512_Update); + break; +#endif + default: + return atom_notsup; + } + ASSERT(ctx_size); + ASSERT(ctx_update); + + if (ctx.size != ctx_size) { + return enif_make_badarg(env); + } + + ctx_buff = enif_make_new_binary(env, ctx_size, &new_ctx); + memcpy(ctx_buff, ctx.data, ctx_size); + ctx_update(ctx_buff, data.data, data.size); + + CONSUME_REDS(env, data); + return enif_make_tuple2(env, tuple[0], new_ctx); +} + +ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* ({Type, Context}) */ + typedef int (*final_fun)(unsigned char*, void*); + ERL_NIF_TERM ret; + ErlNifBinary ctx; + const ERL_NIF_TERM *tuple; + int arity; + struct digest_type_t *digp = NULL; + const EVP_MD *md; + void *new_ctx; + size_t ctx_size = 0; + final_fun ctx_final = 0; + + if (!enif_get_tuple(env, argv[0], &arity, &tuple) || + arity != 2 || + !(digp = get_digest_type(tuple[0])) || + !enif_inspect_binary(env, tuple[1], &ctx)) { + return enif_make_badarg(env); + } + md = digp->md.p; + if (!md) { + return atom_notsup; + } + + switch (EVP_MD_type(md)) + { + case NID_md4: + ctx_size = MD4_CTX_LEN; + ctx_final = (final_fun)(&MD4_Final); + break; + case NID_md5: + ctx_size = MD5_CTX_LEN; + ctx_final = (final_fun)(&MD5_Final); + break; + case NID_ripemd160: + ctx_size = RIPEMD160_CTX_LEN; + ctx_final = (final_fun)(&RIPEMD160_Final); + break; + case NID_sha1: + ctx_size = sizeof(SHA_CTX); + ctx_final = (final_fun)(&SHA1_Final); + break; +#ifdef HAVE_SHA224 + case NID_sha224: + ctx_size = sizeof(SHA256_CTX); + ctx_final = (final_fun)(&SHA224_Final); + break; +#endif +#ifdef HAVE_SHA256 + case NID_sha256: + ctx_size = sizeof(SHA256_CTX); + ctx_final = (final_fun)(&SHA256_Final); + break; +#endif +#ifdef HAVE_SHA384 + case NID_sha384: + ctx_size = sizeof(SHA512_CTX); + ctx_final = (final_fun)(&SHA384_Final); + break; +#endif +#ifdef HAVE_SHA512 + case NID_sha512: + ctx_size = sizeof(SHA512_CTX); + ctx_final = (final_fun)(&SHA512_Final); + break; +#endif + default: + return atom_notsup; + } + ASSERT(ctx_size); + ASSERT(ctx_final); + + if (ctx.size != ctx_size) { + return enif_make_badarg(env); + } + + new_ctx = enif_alloc(ctx_size); + memcpy(new_ctx, ctx.data, ctx_size); + ctx_final(enif_make_new_binary(env, (size_t)EVP_MD_size(md), &ret), + new_ctx); + enif_free(new_ctx); + + return ret; +} + +#endif /* OPENSSL_VERSION_NUMBER < 1.0 */ diff --git a/lib/crypto/c_src/hash.h b/lib/crypto/c_src/hash.h new file mode 100644 index 0000000000..8bae07f39a --- /dev/null +++ b/lib/crypto/c_src/hash.h @@ -0,0 +1,33 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_HASH_H__ +#define E_HASH_H__ 1 + +#include "common.h" + +int init_hash_ctx(ErlNifEnv *env); + +ERL_NIF_TERM hash_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM hash_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM hash_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM hash_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_HASH_H__ */ diff --git a/lib/crypto/c_src/hmac.c b/lib/crypto/c_src/hmac.c new file mode 100644 index 0000000000..143cde90e1 --- /dev/null +++ b/lib/crypto/c_src/hmac.c @@ -0,0 +1,185 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "hmac.h" +#include "digest.h" + +struct hmac_context +{ + ErlNifMutex* mtx; + int alive; + HMAC_CTX* ctx; +}; + +static ErlNifResourceType* hmac_context_rtype; + +static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context*); + +int init_hmac_ctx(ErlNifEnv *env) { + hmac_context_rtype = enif_open_resource_type(env, NULL, "hmac_context", + (ErlNifResourceDtor*) hmac_context_dtor, + ERL_NIF_RT_CREATE|ERL_NIF_RT_TAKEOVER, + NULL); + if (hmac_context_rtype == NULL) { + PRINTF_ERR0("CRYPTO: Could not open resource type 'hmac_context'"); + return 0; + } + return 1; +} + +ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type, Key, Data) or (Type, Key, Data, MacSize) */ + struct digest_type_t *digp = NULL; + ErlNifBinary key, data; + unsigned char buff[EVP_MAX_MD_SIZE]; + unsigned size = 0, req_size = 0; + ERL_NIF_TERM ret; + + digp = get_digest_type(argv[0]); + if (!digp || + !enif_inspect_iolist_as_binary(env, argv[1], &key) || + !enif_inspect_iolist_as_binary(env, argv[2], &data) || + (argc == 4 && !enif_get_uint(env, argv[3], &req_size))) { + return enif_make_badarg(env); + } + + if (!digp->md.p || + !HMAC(digp->md.p, + key.data, key.size, + data.data, data.size, + buff, &size)) { + return atom_notsup; + } + ASSERT(0 < size && size <= EVP_MAX_MD_SIZE); + CONSUME_REDS(env, data); + + if (argc == 4) { + if (req_size <= size) { + size = req_size; + } + else { + return enif_make_badarg(env); + } + } + memcpy(enif_make_new_binary(env, size, &ret), buff, size); + return ret; +} + +static void hmac_context_dtor(ErlNifEnv* env, struct hmac_context *obj) +{ + if (obj->alive) { + HMAC_CTX_free(obj->ctx); + obj->alive = 0; + } + enif_mutex_destroy(obj->mtx); +} + +ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Type, Key) */ + struct digest_type_t *digp = NULL; + ErlNifBinary key; + ERL_NIF_TERM ret; + struct hmac_context *obj; + + digp = get_digest_type(argv[0]); + if (!digp || + !enif_inspect_iolist_as_binary(env, argv[1], &key)) { + return enif_make_badarg(env); + } + if (!digp->md.p) { + return atom_notsup; + } + + obj = enif_alloc_resource(hmac_context_rtype, sizeof(struct hmac_context)); + obj->mtx = enif_mutex_create("crypto.hmac"); + obj->alive = 1; + obj->ctx = HMAC_CTX_new(); +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) + // Check the return value of HMAC_Init: it may fail in FIPS mode + // for disabled algorithms + if (!HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL)) { + enif_release_resource(obj); + return atom_notsup; + } +#else + HMAC_Init_ex(obj->ctx, key.data, key.size, digp->md.p, NULL); +#endif + + ret = enif_make_resource(env, obj); + enif_release_resource(obj); + return ret; +} + +ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Context, Data) */ + ErlNifBinary data; + struct hmac_context* obj; + + if (!enif_get_resource(env, argv[0], hmac_context_rtype, (void**)&obj) + || !enif_inspect_iolist_as_binary(env, argv[1], &data)) { + return enif_make_badarg(env); + } + enif_mutex_lock(obj->mtx); + if (!obj->alive) { + enif_mutex_unlock(obj->mtx); + return enif_make_badarg(env); + } + HMAC_Update(obj->ctx, data.data, data.size); + enif_mutex_unlock(obj->mtx); + + CONSUME_REDS(env,data); + return argv[0]; +} + +ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Context) or (Context, HashLen) */ + ERL_NIF_TERM ret; + struct hmac_context* obj; + unsigned char mac_buf[EVP_MAX_MD_SIZE]; + unsigned char * mac_bin; + unsigned int req_len = 0; + unsigned int mac_len; + + if (!enif_get_resource(env,argv[0],hmac_context_rtype, (void**)&obj) + || (argc == 2 && !enif_get_uint(env, argv[1], &req_len))) { + return enif_make_badarg(env); + } + + enif_mutex_lock(obj->mtx); + if (!obj->alive) { + enif_mutex_unlock(obj->mtx); + return enif_make_badarg(env); + } + + HMAC_Final(obj->ctx, mac_buf, &mac_len); + HMAC_CTX_free(obj->ctx); + obj->alive = 0; + enif_mutex_unlock(obj->mtx); + + if (argc == 2 && req_len < mac_len) { + /* Only truncate to req_len bytes if asked. */ + mac_len = req_len; + } + mac_bin = enif_make_new_binary(env, mac_len, &ret); + memcpy(mac_bin, mac_buf, mac_len); + + return ret; +} + diff --git a/lib/crypto/c_src/hmac.h b/lib/crypto/c_src/hmac.h new file mode 100644 index 0000000000..1f0e0ca632 --- /dev/null +++ b/lib/crypto/c_src/hmac.h @@ -0,0 +1,33 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_HMAC_H__ +#define E_HMAC_H__ 1 + +#include "common.h" + +int init_hmac_ctx(ErlNifEnv *env); + +ERL_NIF_TERM hmac_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM hmac_init_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM hmac_update_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM hmac_final_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_HMAC_H__ */ diff --git a/lib/crypto/c_src/info.c b/lib/crypto/c_src/info.c new file mode 100644 index 0000000000..3f3194081d --- /dev/null +++ b/lib/crypto/c_src/info.c @@ -0,0 +1,81 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "info.h" + +#ifdef HAVE_DYNAMIC_CRYPTO_LIB + +# if defined(DEBUG) +char *crypto_callback_name = "crypto_callback.debug"; +# elif defined(VALGRIND) +char *crypto_callback_name = "crypto_callback.valgrind"; +# else +char *crypto_callback_name = "crypto_callback"; +# endif + +int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile) +{ + int i; + + for (i = bin->size; i > 0; i--) { + if (bin->data[i-1] == '/') + break; + } + if (i + strlen(newfile) >= bufsz) { + PRINTF_ERR0("CRYPTO: lib name too long"); + return 0; + } + memcpy(buf, bin->data, i); + strcpy(buf+i, newfile); + return 1; +} + +void error_handler(void* null, const char* errstr) +{ + PRINTF_ERR1("CRYPTO LOADING ERROR: '%s'", errstr); +} +#endif /* HAVE_DYNAMIC_CRYPTO_LIB */ + +ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */ + + static const char libname[] = "OpenSSL"; + unsigned name_sz = strlen(libname); + const char* ver = SSLeay_version(SSLEAY_VERSION); + unsigned ver_sz = strlen(ver); + ERL_NIF_TERM name_term, ver_term; + int ver_num = OPENSSL_VERSION_NUMBER; + /* R16: + * Ignore library version number from SSLeay() and instead show header + * version. Otherwise user might try to call a function that is implemented + * by a newer library but not supported by the headers used at compile time. + * Example: DES_ede3_cfb_encrypt in 0.9.7i but not in 0.9.7d. + * + * Version string is still from library though. + */ + + memcpy(enif_make_new_binary(env, name_sz, &name_term), libname, name_sz); + memcpy(enif_make_new_binary(env, ver_sz, &ver_term), ver, ver_sz); + + return enif_make_list1(env, enif_make_tuple3(env, name_term, + enif_make_int(env, ver_num), + ver_term)); +} diff --git a/lib/crypto/c_src/info.h b/lib/crypto/c_src/info.h new file mode 100644 index 0000000000..4f8822ddd7 --- /dev/null +++ b/lib/crypto/c_src/info.h @@ -0,0 +1,35 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_INFO_H__ +#define E_INFO_H__ 1 + +#include "common.h" + +#ifdef HAVE_DYNAMIC_CRYPTO_LIB +extern char *crypto_callback_name; + +int change_basename(ErlNifBinary* bin, char* buf, int bufsz, const char* newfile); +void error_handler(void* null, const char* errstr); +#endif + +ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_INFO_H__ */ diff --git a/lib/crypto/c_src/math.c b/lib/crypto/c_src/math.c new file mode 100644 index 0000000000..7d7d146ca9 --- /dev/null +++ b/lib/crypto/c_src/math.c @@ -0,0 +1,43 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "math.h" + +ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Data1, Data2) */ + ErlNifBinary d1, d2; + unsigned char* ret_ptr; + int i; + ERL_NIF_TERM ret; + + if (!enif_inspect_iolist_as_binary(env,argv[0], &d1) + || !enif_inspect_iolist_as_binary(env,argv[1], &d2) + || d1.size != d2.size) { + return enif_make_badarg(env); + } + ret_ptr = enif_make_new_binary(env, d1.size, &ret); + + for (i=0; i<d1.size; i++) { + ret_ptr[i] = d1.data[i] ^ d2.data[i]; + } + CONSUME_REDS(env,d1); + return ret; +} + diff --git a/lib/crypto/c_src/math.h b/lib/crypto/c_src/math.h new file mode 100644 index 0000000000..b8d68ea654 --- /dev/null +++ b/lib/crypto/c_src/math.h @@ -0,0 +1,28 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_MATH_H__ +#define E_MATH_H__ 1 + +#include "common.h" + +ERL_NIF_TERM do_exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_MATH_H__ */ diff --git a/lib/crypto/c_src/openssl_config.h b/lib/crypto/c_src/openssl_config.h new file mode 100644 index 0000000000..2e5f5b22c1 --- /dev/null +++ b/lib/crypto/c_src/openssl_config.h @@ -0,0 +1,337 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_OPENSSL_CONFIG_H__ +#define E_OPENSSL_CONFIG_H__ 1 + +#define OPENSSL_THREAD_DEFINES +#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> +#include <openssl/aes.h> +#include <openssl/md5.h> +#include <openssl/md4.h> +#include <openssl/sha.h> +#include <openssl/ripemd.h> +#include <openssl/bn.h> +#include <openssl/objects.h> +#ifndef OPENSSL_NO_RC4 + #include <openssl/rc4.h> +#endif /* OPENSSL_NO_RC4 */ +#ifndef OPENSSL_NO_RC2 + #include <openssl/rc2.h> +#endif +#include <openssl/blowfish.h> +#include <openssl/rand.h> +#include <openssl/evp.h> +#include <openssl/hmac.h> +#include <openssl/err.h> + +/* Helper macro to construct a OPENSSL_VERSION_NUMBER. + * See openssl/opensslv.h + */ +#define PACKED_OPENSSL_VERSION(MAJ, MIN, FIX, P) \ + ((((((((MAJ << 8) | MIN) << 8 ) | FIX) << 8) | (P-'a'+1)) << 4) | 0xf) + +#define PACKED_OPENSSL_VERSION_PLAIN(MAJ, MIN, FIX) \ + PACKED_OPENSSL_VERSION(MAJ,MIN,FIX,('a'-1)) + + +/* LibreSSL was cloned from OpenSSL 1.0.1g and claims to be API and BPI compatible + * with 1.0.1. + * + * LibreSSL has the same names on include files and symbols as OpenSSL, but defines + * the OPENSSL_VERSION_NUMBER to be >= 2.0.0 + * + * Therefor works tests like this as intendend: + * OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) + * (The test is for example "2.4.2" >= "1.0.0" although the test + * with the cloned OpenSSL test would be "1.0.1" >= "1.0.0") + * + * But tests like this gives wrong result: + * OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) + * (The test is false since "2.4.2" < "1.1.0". It should have been + * true because the LibreSSL API version is "1.0.1") + * + */ + +#ifdef LIBRESSL_VERSION_NUMBER +/* A macro to test on in this file */ +#define HAS_LIBRESSL +#endif + +#ifdef HAS_LIBRESSL +/* LibreSSL dislikes FIPS */ +# ifdef FIPS_SUPPORT +# undef FIPS_SUPPORT +# endif + +# if LIBRESSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(2,7,0) +/* LibreSSL wants the 1.0.1 API */ +# define NEED_EVP_COMPATIBILITY_FUNCTIONS +# endif +#endif + + +#if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) +# define NEED_EVP_COMPATIBILITY_FUNCTIONS +#endif + + +#ifndef HAS_LIBRESSL +# if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) +# define HAS_EVP_PKEY_CTX +# endif +#endif + + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) +#include <openssl/modes.h> +#endif + +#include "crypto_callback.h" + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ + && !defined(OPENSSL_NO_SHA224) && defined(NID_sha224) \ + && !defined(OPENSSL_NO_SHA256) /* disabled like this in my sha.h (?) */ +# define HAVE_SHA224 +#endif +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ + && !defined(OPENSSL_NO_SHA256) && defined(NID_sha256) +# define HAVE_SHA256 +#endif +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ + && !defined(OPENSSL_NO_SHA384) && defined(NID_sha384)\ + && !defined(OPENSSL_NO_SHA512) /* disabled like this in my sha.h (?) */ +# define HAVE_SHA384 +#endif +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(0,9,8) \ + && !defined(OPENSSL_NO_SHA512) && defined(NID_sha512) +# define HAVE_SHA512 +#endif +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,7,'e') +# define HAVE_DES_ede3_cfb_encrypt +#endif + +// SHA3: +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,1) +// An error in beta releases of 1.1.1 fixed in production release +# ifdef NID_sha3_224 +# define HAVE_SHA3_224 +# endif +# ifdef NID_sha3_256 +# define HAVE_SHA3_256 +# endif +#endif +# ifdef NID_sha3_384 +# define HAVE_SHA3_384 +# endif +# ifdef NID_sha3_512 +# define HAVE_SHA3_512 +# endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \ + && !defined(OPENSSL_NO_EC) \ + && !defined(OPENSSL_NO_ECDH) \ + && !defined(OPENSSL_NO_ECDSA) +# define HAVE_EC +#endif + +// (test for >= 1.1.1pre8) +#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1) -7) \ + && !defined(HAS_LIBRESSL) \ + && defined(HAVE_EC) +# define HAVE_ED_CURVE_DH +# if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)) +# define HAVE_EDDSA +# endif +#endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'c') +# define HAVE_AES_IGE +#endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1) +# define HAVE_EVP_AES_CTR +# define HAVE_AEAD +# define HAVE_GCM +# define HAVE_CCM +# define HAVE_CMAC +# if defined(RSA_PKCS1_OAEP_PADDING) +# define HAVE_RSA_OAEP_PADDING +# endif +# define HAVE_RSA_MGF1_MD +# if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d') +# define HAVE_GCM_EVP_DECRYPT_BUG +# endif +#endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0) +# ifndef HAS_LIBRESSL +# define HAVE_CHACHA20 +# define HAVE_CHACHA20_POLY1305 +# define HAVE_RSA_OAEP_MD +# endif +#endif + +// OPENSSL_VERSION_NUMBER >= 1.1.1-pre8 +#if OPENSSL_VERSION_NUMBER >= (PACKED_OPENSSL_VERSION_PLAIN(1,1,1)-7) +# ifndef HAS_LIBRESSL +# define HAVE_POLY1305 +# endif +#endif + +#if OPENSSL_VERSION_NUMBER <= PACKED_OPENSSL_VERSION(0,9,8,'l') +# define HAVE_ECB_IVEC_BUG +#endif + +#ifndef HAS_LIBRESSL +# ifdef RSA_SSLV23_PADDING +# define HAVE_RSA_SSLV23_PADDING +# endif +#endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,0) +# ifdef RSA_PKCS1_PSS_PADDING +# define HAVE_RSA_PKCS1_PSS_PADDING +# endif +#endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'h') \ + && defined(HAVE_EC) +/* If OPENSSL_NO_EC is set, there will be an error in ec.h included from engine.h + So if EC is disabled, you can't use Engine either.... +*/ +# define HAS_ENGINE_SUPPORT +#endif + + +#if defined(HAS_ENGINE_SUPPORT) +# include <openssl/engine.h> +#endif + +#if defined(HAVE_CMAC) +#include <openssl/cmac.h> +#endif + +#if defined(HAVE_EC) +#include <openssl/ec.h> +#include <openssl/ecdh.h> +#include <openssl/ecdsa.h> +#endif + +#ifdef VALGRIND + # include <valgrind/memcheck.h> + +/* libcrypto mixes supplied buffer contents into its entropy pool, + which makes valgrind complain about the use of uninitialized data. + We use this valgrind "request" to make sure that no such seemingly + undefined data is returned. +*/ + # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \ + VALGRIND_MAKE_MEM_DEFINED(ptr,size) + + # define ERL_VALGRIND_ASSERT_MEM_DEFINED(Ptr,Size) \ + do { \ + int __erl_valgrind_mem_defined = VALGRIND_CHECK_MEM_IS_DEFINED((Ptr),(Size)); \ + if (__erl_valgrind_mem_defined != 0) { \ + fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%ld) failed at %s:%d\r\n", \ + (Ptr),(long)(Size), __FILE__, __LINE__); \ + abort(); \ + } \ + } while (0) + +#else + # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) + # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size) +#endif + +#ifdef DEBUG + # define ASSERT(e) \ + ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\ + #e, __FILE__, __LINE__), abort(), 0))) +#else + # define ASSERT(e) ((void) 1) +#endif + +#ifdef __GNUC__ + # define INLINE __inline__ +#elif defined(__WIN32__) + # define INLINE __forceinline +#else + # define INLINE +#endif + + +#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \ + (((unsigned char*) (s))[1] << 16) | \ + (((unsigned char*) (s))[2] << 8) | \ + (((unsigned char*) (s))[3])) + +#define put_int32(s,i) \ +{ (s)[0] = (char)(((i) >> 24) & 0xff);\ + (s)[1] = (char)(((i) >> 16) & 0xff);\ + (s)[2] = (char)(((i) >> 8) & 0xff);\ + (s)[3] = (char)((i) & 0xff);\ +} + +/* This shall correspond to the similar macro in crypto.erl */ +/* Current value is: erlang:system_info(context_reductions) * 10 */ +#define MAX_BYTES_TO_NIF 20000 + +#define CONSUME_REDS(NifEnv, Ibin) \ +do { \ + int _cost = ((Ibin).size * 100) / MAX_BYTES_TO_NIF;\ + if (_cost) { \ + (void) enif_consume_timeslice((NifEnv), \ + (_cost > 100) ? 100 : _cost); \ + } \ + } while (0) + +#ifdef NEED_EVP_COMPATIBILITY_FUNCTIONS +# include "evp_compat.h" +#else +# define HAVE_OPAQUE_BN_GENCB +#endif + +/* +#define PRINTF_ERR0(FMT) enif_fprintf(stderr, FMT "\n") +#define PRINTF_ERR1(FMT, A1) enif_fprintf(stderr, FMT "\n", A1) +#define PRINTF_ERR2(FMT, A1, A2) enif_fprintf(stderr, FMT "\n", A1, A2) +*/ + +#define PRINTF_ERR0(FMT) +#define PRINTF_ERR1(FMT,A1) +#define PRINTF_ERR2(FMT,A1,A2) + +#ifdef FIPS_SUPPORT +/* In FIPS mode non-FIPS algorithms are disabled and return badarg. */ +#define CHECK_NO_FIPS_MODE() { if (FIPS_mode()) return atom_notsup; } +#else +#define CHECK_NO_FIPS_MODE() +#endif + +#endif /* E_OPENSSL_CONFIG_H__ */ diff --git a/lib/crypto/c_src/otp_test_engine.c b/lib/crypto/c_src/otp_test_engine.c index 34c825059f..2c8cce094e 100644 --- a/lib/crypto/c_src/otp_test_engine.c +++ b/lib/crypto/c_src/otp_test_engine.c @@ -35,7 +35,12 @@ #if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION_PLAIN(1,1,0) \ || defined(LIBRESSL_VERSION_NUMBER) -#define OLD +# define OLD +#endif + +#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,1,0) \ + && !defined(LIBRESSL_VERSION_NUMBER) +# define FAKE_RSA_IMPL #endif #if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION(0,9,8,'o') \ @@ -56,13 +61,41 @@ static const char *test_engine_id = "MD5"; static const char *test_engine_name = "MD5 test engine"; -/* The callback that does the job of fetching keys on demand by the Engine */ -EVP_PKEY* test_key_load(ENGINE *er, const char *id, UI_METHOD *ui_method, void *callback_data); +#if defined(FAKE_RSA_IMPL) +/*-------- test of private/public keys and RSA in engine ---------*/ +static RSA_METHOD *test_rsa_method = NULL; + +/* Our on "RSA" implementation */ +static int test_rsa_sign(int dtype, const unsigned char *m, + unsigned int m_len, unsigned char *sigret, + unsigned int *siglen, const RSA *rsa); +static int test_rsa_verify(int dtype, const unsigned char *m, + unsigned int m_len, const unsigned char *sigret, + unsigned int siglen, const RSA *rsa); +static int test_rsa_free(RSA *rsa); +#endif /* if defined(FAKE_RSA_IMPL) */ + +/* The callbacks that does the job of fetching keys on demand by the Engine */ +EVP_PKEY* test_privkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data); +EVP_PKEY* test_pubkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data); +EVP_PKEY* test_key_load(ENGINE *er, const char *id, UI_METHOD *ui_method, void *callback_data, int priv); + +/*----------------------------------------------------------------*/ static int test_init(ENGINE *e) { printf("OTP Test Engine Initializatzion!\r\n"); +#if defined(FAKE_RSA_IMPL) + if ( !RSA_meth_set_finish(test_rsa_method, test_rsa_free) + || !RSA_meth_set_sign(test_rsa_method, test_rsa_sign) + || !RSA_meth_set_verify(test_rsa_method, test_rsa_verify) + ) { + fprintf(stderr, "Setup RSA_METHOD failed\r\n"); + return 0; + } +#endif /* if defined(FAKE_RSA_IMPL) */ + /* Load all digest and cipher algorithms. Needed for password protected private keys */ OpenSSL_add_all_ciphers(); OpenSSL_add_all_digests(); @@ -79,6 +112,19 @@ static void add_test_data(unsigned char *md, unsigned int len) } } +#if defined(FAKE_RSA_IMPL) +static int chk_test_data(const unsigned char *md, unsigned int len) +{ + unsigned int i; + + for (i=0; i<len; i++) { + if (md[i] != (unsigned char)(i & 0xff)) + return 0; + } + return 1; +} +#endif /* if defined(FAKE_RSA_IMPL) */ + /* MD5 part */ #undef data #ifdef OLD @@ -184,19 +230,34 @@ static int test_engine_digest_selector(ENGINE *e, const EVP_MD **digest, return ok; } - static int bind_helper(ENGINE * e, const char *id) { - if (!ENGINE_set_id(e, test_engine_id) || - !ENGINE_set_name(e, test_engine_name) || - !ENGINE_set_init_function(e, test_init) || - !ENGINE_set_digests(e, &test_engine_digest_selector) || +#if defined(FAKE_RSA_IMPL) + test_rsa_method = RSA_meth_new("OTP test RSA method", 0); + if (test_rsa_method == NULL) { + fprintf(stderr, "RSA_meth_new failed\r\n"); + return 0; + } +#endif /* if defined(FAKE_RSA_IMPL) */ + + if (!ENGINE_set_id(e, test_engine_id) + || !ENGINE_set_name(e, test_engine_name) + || !ENGINE_set_init_function(e, test_init) + || !ENGINE_set_digests(e, &test_engine_digest_selector) /* For testing of key storage in an Engine: */ - !ENGINE_set_load_privkey_function(e, &test_key_load) || - !ENGINE_set_load_pubkey_function(e, &test_key_load) - ) + || !ENGINE_set_load_privkey_function(e, &test_privkey_load) + || !ENGINE_set_load_pubkey_function(e, &test_pubkey_load) + ) return 0; +#if defined(FAKE_RSA_IMPL) + if ( !ENGINE_set_RSA(e, test_rsa_method) ) { + RSA_meth_free(test_rsa_method); + test_rsa_method = NULL; + return 0; + } +#endif /* if defined(FAKE_RSA_IMPL) */ + return 1; } @@ -211,24 +272,29 @@ IMPLEMENT_DYNAMIC_BIND_FN(bind_helper); */ int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password); -EVP_PKEY* test_key_load(ENGINE *er, const char *id, UI_METHOD *ui_method, void *callback_data) +EVP_PKEY* test_privkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data) { + return test_key_load(eng, id, ui_method, callback_data, 1); +} + +EVP_PKEY* test_pubkey_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data) { + return test_key_load(eng, id, ui_method, callback_data, 0); +} + +EVP_PKEY* test_key_load(ENGINE *eng, const char *id, UI_METHOD *ui_method, void *callback_data, int priv) { EVP_PKEY *pkey = NULL; FILE *f = fopen(id, "r"); if (!f) { - fprintf(stderr, "%s:%d fopen(%s) failed\r\n", __FILE__,__LINE__,id); - return NULL; + fprintf(stderr, "%s:%d fopen(%s) failed\r\n", __FILE__,__LINE__,id); + return NULL; } - /* First try to read as a private key. If that fails, try to read as a public key: */ - pkey = PEM_read_PrivateKey(f, NULL, pem_passwd_cb_fun, callback_data); - if (!pkey) { - /* ERR_print_errors_fp (stderr); */ - fclose(f); - f = fopen(id, "r"); - pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); - } + pkey = + priv + ? PEM_read_PrivateKey(f, NULL, pem_passwd_cb_fun, callback_data) + : PEM_read_PUBKEY(f, NULL, NULL, NULL); + fclose(f); if (!pkey) { @@ -278,3 +344,71 @@ int pem_passwd_cb_fun(char *buf, int size, int rwflag, void *password) } #endif + +#if defined(FAKE_RSA_IMPL) +/* RSA sign. This returns a fixed string so the test case can test that it was called + instead of the cryptolib default RSA sign */ + +unsigned char fake_flag[] = {255,3,124,180,35,10,180,151,101,247,62,59,80,122,220, + 142,24,180,191,34,51,150,112,27,43,142,195,60,245,213,80,179}; + +int test_rsa_sign(int dtype, + /* The digest to sign */ + const unsigned char *m, unsigned int m_len, + /* The allocated buffer to fill with the signature */ + unsigned char *sigret, unsigned int *siglen, + /* The key */ + const RSA *rsa) +{ + int slen; + fprintf(stderr, "test_rsa_sign (dtype=%i) called m_len=%u *siglen=%u\r\n", dtype, m_len, *siglen); + if (!sigret) { + fprintf(stderr, "sigret = NULL\r\n"); + return -1; + } + + /* {int i; + fprintf(stderr, "Digest =\r\n"); + for(i=0; i<m_len; i++) + fprintf(stderr, "%i,", m[i]); + fprintf(stderr, "\r\n"); + } */ + + if ((sizeof(fake_flag) == m_len) + && bcmp(m,fake_flag,m_len) == 0) { + printf("To be faked\r\n"); + /* To be faked */ + slen = RSA_size(rsa); + add_test_data(sigret, slen); /* The signature is 0,1,2...255,0,1... */ + *siglen = slen; /* Must set this. Why? */ + return 1; /* 1 = success */ + } + return 0; +} + +int test_rsa_verify(int dtype, + /* The digest to verify */ + const unsigned char *m, unsigned int m_len, + /* The signature */ + const unsigned char *sigret, unsigned int siglen, + /* The key */ + const RSA *rsa) +{ + printf("test_rsa_verify (dtype=%i) called m_len=%u siglen=%u\r\n", dtype, m_len, siglen); + + if ((sizeof(fake_flag) == m_len) + && bcmp(m,fake_flag,m_len) == 0) { + printf("To be faked\r\n"); + return (siglen == RSA_size(rsa)) + && chk_test_data(sigret, siglen); + } + return 0; +} + +static int test_rsa_free(RSA *rsa) +{ + printf("test_rsa_free called\r\n"); + return 1; +} + +#endif /* if defined(FAKE_RSA_IMPL) */ diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c new file mode 100644 index 0000000000..bd56b2d977 --- /dev/null +++ b/lib/crypto/c_src/pkey.c @@ -0,0 +1,1170 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "pkey.h" +#include "bn.h" +#include "digest.h" +#include "dss.h" +#include "ec.h" +#include "eddsa.h" +#include "engine.h" +#include "rsa.h" + +#define PKEY_BADARG -1 +#define PKEY_NOTSUP 0 +#define PKEY_OK 1 + +typedef struct PKeyCryptOptions { + const EVP_MD *rsa_mgf1_md; + ErlNifBinary rsa_oaep_label; + const EVP_MD *rsa_oaep_md; + int rsa_padding; + const EVP_MD *signature_md; +} PKeyCryptOptions; + +typedef struct PKeySignOptions { + const EVP_MD *rsa_mgf1_md; + int rsa_padding; + int rsa_pss_saltlen; +} PKeySignOptions; + + +static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type, + const EVP_MD **md); +static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm, + ERL_NIF_TERM type, ERL_NIF_TERM data, + unsigned char *md_value, const EVP_MD **mdp, + unsigned char **tbsp, size_t *tbslenp); +static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, + const EVP_MD *md, PKeySignOptions *opt); +static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey); +static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, + EVP_PKEY **pkey); +static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, + PKeyCryptOptions *opt); +static size_t size_of_RSA(EVP_PKEY *pkey); + + +static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM type, + const EVP_MD **md) +{ + struct digest_type_t *digp = NULL; + *md = NULL; + + if (type == atom_none && algorithm == atom_rsa) return PKEY_OK; +#ifdef HAVE_EDDSA + if (algorithm == atom_eddsa) return PKEY_OK; +#endif + digp = get_digest_type(type); + if (!digp) return PKEY_BADARG; + if (!digp->md.p) return PKEY_NOTSUP; + + *md = digp->md.p; + return PKEY_OK; +} + +static int get_pkey_sign_digest(ErlNifEnv *env, ERL_NIF_TERM algorithm, + ERL_NIF_TERM type, ERL_NIF_TERM data, + unsigned char *md_value, const EVP_MD **mdp, + unsigned char **tbsp, size_t *tbslenp) +{ + int i; + const ERL_NIF_TERM *tpl_terms; + int tpl_arity; + ErlNifBinary tbs_bin; + EVP_MD_CTX *mdctx; + const EVP_MD *md = *mdp; + unsigned char *tbs = *tbsp; + size_t tbslen = *tbslenp; + unsigned int tbsleni; + + if ((i = get_pkey_digest_type(env, algorithm, type, &md)) != PKEY_OK) { + return i; + } + if (enif_get_tuple(env, data, &tpl_arity, &tpl_terms)) { + if (tpl_arity != 2 || tpl_terms[0] != atom_digest + || !enif_inspect_binary(env, tpl_terms[1], &tbs_bin) + || (md != NULL && tbs_bin.size != EVP_MD_size(md))) { + return PKEY_BADARG; + } + /* We have a digest (= hashed text) in tbs_bin */ + tbs = tbs_bin.data; + tbslen = tbs_bin.size; + } else if (md == NULL) { + if (!enif_inspect_binary(env, data, &tbs_bin)) { + return PKEY_BADARG; + } + /* md == NULL, that is no hashing because DigestType argument was atom_none */ + tbs = tbs_bin.data; + tbslen = tbs_bin.size; + } else { + if (!enif_inspect_binary(env, data, &tbs_bin)) { + return PKEY_BADARG; + } + /* We have the cleartext in tbs_bin and the hash algo info in md */ + tbs = md_value; + mdctx = EVP_MD_CTX_create(); + if (!mdctx) { + return PKEY_BADARG; + } + /* Looks well, now hash the plain text into a digest according to md */ + if (EVP_DigestInit_ex(mdctx, md, NULL) <= 0) { + EVP_MD_CTX_destroy(mdctx); + return PKEY_BADARG; + } + if (EVP_DigestUpdate(mdctx, tbs_bin.data, tbs_bin.size) <= 0) { + EVP_MD_CTX_destroy(mdctx); + return PKEY_BADARG; + } + if (EVP_DigestFinal_ex(mdctx, tbs, &tbsleni) <= 0) { + EVP_MD_CTX_destroy(mdctx); + return PKEY_BADARG; + } + tbslen = (size_t)(tbsleni); + EVP_MD_CTX_destroy(mdctx); + } + + *mdp = md; + *tbsp = tbs; + *tbslenp = tbslen; + + return PKEY_OK; +} + +static int get_pkey_sign_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, + const EVP_MD *md, PKeySignOptions *opt) +{ + ERL_NIF_TERM head, tail; + const ERL_NIF_TERM *tpl_terms; + int tpl_arity; + const EVP_MD *opt_md; + int i; + + if (!enif_is_list(env, options)) { + return PKEY_BADARG; + } + + /* defaults */ + if (algorithm == atom_rsa) { + opt->rsa_mgf1_md = NULL; + opt->rsa_padding = RSA_PKCS1_PADDING; + opt->rsa_pss_saltlen = -2; + } + + if (enif_is_empty_list(env, options)) { + return PKEY_OK; + } + + if (algorithm == atom_rsa) { + tail = options; + while (enif_get_list_cell(env, tail, &head, &tail)) { + if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) { + if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) { + i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); + if (i != PKEY_OK) { + return i; + } + opt->rsa_mgf1_md = opt_md; + } else if (tpl_terms[0] == atom_rsa_padding) { + if (tpl_terms[1] == atom_rsa_pkcs1_padding) { + opt->rsa_padding = RSA_PKCS1_PADDING; + } else if (tpl_terms[1] == atom_rsa_pkcs1_pss_padding) { +#ifdef HAVE_RSA_PKCS1_PSS_PADDING + opt->rsa_padding = RSA_PKCS1_PSS_PADDING; + if (opt->rsa_mgf1_md == NULL) { + opt->rsa_mgf1_md = md; + } +#else + return PKEY_NOTSUP; +#endif + } else if (tpl_terms[1] == atom_rsa_x931_padding) { + opt->rsa_padding = RSA_X931_PADDING; + } else if (tpl_terms[1] == atom_rsa_no_padding) { + opt->rsa_padding = RSA_NO_PADDING; + } else { + return PKEY_BADARG; + } + } else if (tpl_terms[0] == atom_rsa_pss_saltlen) { + if (!enif_get_int(env, tpl_terms[1], &(opt->rsa_pss_saltlen)) + || opt->rsa_pss_saltlen < -2) { + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } + } + } else { + return PKEY_BADARG; + } + + return PKEY_OK; +} + +static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, EVP_PKEY **pkey) +{ + if (enif_is_map(env, key)) { +#ifdef HAS_ENGINE_SUPPORT + /* Use key stored in engine */ + ENGINE *e; + char *id = NULL; + char *password; + + if (!get_engine_and_key_id(env, key, &id, &e)) + return PKEY_BADARG; + password = get_key_password(env, key); + *pkey = ENGINE_load_private_key(e, id, NULL, password); + if (password) enif_free(password); + enif_free(id); + if (!*pkey) + return PKEY_BADARG; +#else + return PKEY_BADARG; +#endif + } + else if (algorithm == atom_rsa) { + RSA *rsa = RSA_new(); + + if (!get_rsa_private_key(env, key, rsa)) { + RSA_free(rsa); + return PKEY_BADARG; + } + + *pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_RSA(*pkey, rsa)) { + EVP_PKEY_free(*pkey); + RSA_free(rsa); + return PKEY_BADARG; + } + } else if (algorithm == atom_ecdsa) { +#if defined(HAVE_EC) + EC_KEY *ec = NULL; + const ERL_NIF_TERM *tpl_terms; + int tpl_arity; + + if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2 + && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1]) + && get_ec_key(env, tpl_terms[0], tpl_terms[1], atom_undefined, &ec)) { + + *pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) { + EVP_PKEY_free(*pkey); + EC_KEY_free(ec); + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } +#else + return PKEY_NOTSUP; +#endif + } else if (algorithm == atom_eddsa) { +#if defined(HAVE_EDDSA) + if (!get_eddsa_key(env, 0, key, pkey)) { + return PKEY_BADARG; + } +#else + return PKEY_NOTSUP; +#endif + } else if (algorithm == atom_dss) { + DSA *dsa = DSA_new(); + + if (!get_dss_private_key(env, key, dsa)) { + DSA_free(dsa); + return PKEY_BADARG; + } + + *pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_DSA(*pkey, dsa)) { + EVP_PKEY_free(*pkey); + DSA_free(dsa); + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } + + return PKEY_OK; +} + +static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM key, + EVP_PKEY **pkey) +{ + if (enif_is_map(env, key)) { +#ifdef HAS_ENGINE_SUPPORT + /* Use key stored in engine */ + ENGINE *e; + char *id = NULL; + char *password; + + if (!get_engine_and_key_id(env, key, &id, &e)) + return PKEY_BADARG; + password = get_key_password(env, key); + *pkey = ENGINE_load_public_key(e, id, NULL, password); + if (password) enif_free(password); + enif_free(id); + if (!pkey) + return PKEY_BADARG; +#else + return PKEY_BADARG; +#endif + } else if (algorithm == atom_rsa) { + RSA *rsa = RSA_new(); + + if (!get_rsa_public_key(env, key, rsa)) { + RSA_free(rsa); + return PKEY_BADARG; + } + + *pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_RSA(*pkey, rsa)) { + EVP_PKEY_free(*pkey); + RSA_free(rsa); + return PKEY_BADARG; + } + } else if (algorithm == atom_ecdsa) { +#if defined(HAVE_EC) + EC_KEY *ec = NULL; + const ERL_NIF_TERM *tpl_terms; + int tpl_arity; + + if (enif_get_tuple(env, key, &tpl_arity, &tpl_terms) && tpl_arity == 2 + && enif_is_tuple(env, tpl_terms[0]) && enif_is_binary(env, tpl_terms[1]) + && get_ec_key(env, tpl_terms[0], atom_undefined, tpl_terms[1], &ec)) { + + *pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_EC_KEY(*pkey, ec)) { + EVP_PKEY_free(*pkey); + EC_KEY_free(ec); + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } +#else + return PKEY_NOTSUP; +#endif + } else if (algorithm == atom_eddsa) { +#if defined(HAVE_EDDSA) + if (!get_eddsa_key(env, 1, key, pkey)) { + return PKEY_BADARG; + } +#else + return PKEY_NOTSUP; +#endif + } else if (algorithm == atom_dss) { + DSA *dsa = DSA_new(); + + if (!get_dss_public_key(env, key, dsa)) { + DSA_free(dsa); + return PKEY_BADARG; + } + + *pkey = EVP_PKEY_new(); + if (!EVP_PKEY_assign_DSA(*pkey, dsa)) { + EVP_PKEY_free(*pkey); + DSA_free(dsa); + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } + + return PKEY_OK; +} + +ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{/* (Algorithm, Type, Data|{digest,Digest}, Key|#{}, Options) */ + int i; + const EVP_MD *md = NULL; + unsigned char md_value[EVP_MAX_MD_SIZE]; + EVP_PKEY *pkey; +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX *ctx; + size_t siglen; +#else + unsigned len, siglen; +#endif + PKeySignOptions sig_opt; + ErlNifBinary sig_bin; /* signature */ + unsigned char *tbs; /* data to be signed */ + size_t tbslen; +/*char buf[1024]; +enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf); +enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf); +printf("\r\n"); +*/ + +#ifndef HAS_ENGINE_SUPPORT + if (enif_is_map(env, argv[3])) { + return atom_notsup; + } +#endif + + i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen); + if (i != PKEY_OK) { + if (i == PKEY_NOTSUP) + return atom_notsup; + else + return enif_make_badarg(env); + } + + i = get_pkey_sign_options(env, argv[0], argv[4], md, &sig_opt); + if (i != PKEY_OK) { + if (i == PKEY_NOTSUP) + return atom_notsup; + else + return enif_make_badarg(env); + } + + if (get_pkey_private_key(env, argv[0], argv[3], &pkey) != PKEY_OK) { + return enif_make_badarg(env); + } + +#ifdef HAS_EVP_PKEY_CTX + ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) goto badarg; + + if (argv[0] != atom_eddsa) { + if (EVP_PKEY_sign_init(ctx) <= 0) goto badarg; + if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg; + } + + if (argv[0] == atom_rsa) { + if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg; +# ifdef HAVE_RSA_PKCS1_PSS_PADDING + if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) { + if (sig_opt.rsa_mgf1_md != NULL) { +# ifdef HAVE_RSA_MGF1_MD + if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg; +# else + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return atom_notsup; +# endif + } + if (sig_opt.rsa_pss_saltlen > -2 + && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0) + goto badarg; + } +#endif + } + + if (argv[0] == atom_eddsa) { +#ifdef HAVE_EDDSA + EVP_MD_CTX* mdctx = EVP_MD_CTX_new(); + if (!EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey)) { + if (mdctx) EVP_MD_CTX_free(mdctx); + goto badarg; + } + + if (!EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen)) { + EVP_MD_CTX_free(mdctx); + goto badarg; + } + enif_alloc_binary(siglen, &sig_bin); + + if (!EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen)) { + EVP_MD_CTX_free(mdctx); + goto badarg; + } + EVP_MD_CTX_free(mdctx); +#else + goto badarg; +#endif + } + else + { + if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) <= 0) goto badarg; + enif_alloc_binary(siglen, &sig_bin); + + if (md != NULL) { + ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md)); + } + i = EVP_PKEY_sign(ctx, sig_bin.data, &siglen, tbs, tbslen); + } + + EVP_PKEY_CTX_free(ctx); +#else +/*printf("Old interface\r\n"); + */ + if (argv[0] == atom_rsa) { + RSA *rsa = EVP_PKEY_get1_RSA(pkey); + enif_alloc_binary(RSA_size(rsa), &sig_bin); + len = EVP_MD_size(md); + ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len); + i = RSA_sign(md->type, tbs, len, sig_bin.data, &siglen, rsa); + RSA_free(rsa); + } else if (argv[0] == atom_dss) { + DSA *dsa = EVP_PKEY_get1_DSA(pkey); + enif_alloc_binary(DSA_size(dsa), &sig_bin); + len = EVP_MD_size(md); + ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len); + i = DSA_sign(md->type, tbs, len, sig_bin.data, &siglen, dsa); + DSA_free(dsa); + } else if (argv[0] == atom_ecdsa) { +#if defined(HAVE_EC) + EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); + enif_alloc_binary(ECDSA_size(ec), &sig_bin); + len = EVP_MD_size(md); + ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, len); + i = ECDSA_sign(md->type, tbs, len, sig_bin.data, &siglen, ec); + EC_KEY_free(ec); +#else + EVP_PKEY_free(pkey); + return atom_notsup; +#endif + } else { + goto badarg; + } +#endif + + EVP_PKEY_free(pkey); + if (i == 1) { + ERL_VALGRIND_MAKE_MEM_DEFINED(sig_bin.data, siglen); + if (siglen != sig_bin.size) { + enif_realloc_binary(&sig_bin, siglen); + ERL_VALGRIND_ASSERT_MEM_DEFINED(sig_bin.data, siglen); + } + return enif_make_binary(env, &sig_bin); + } else { + enif_release_binary(&sig_bin); + return atom_error; + } + + badarg: +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX_free(ctx); +#endif + EVP_PKEY_free(pkey); + return enif_make_badarg(env); +} + +ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{/* (Algorithm, Type, Data|{digest,Digest}, Signature, Key, Options) */ + int i; + const EVP_MD *md = NULL; + unsigned char md_value[EVP_MAX_MD_SIZE]; + EVP_PKEY *pkey; +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX *ctx; +#else +#endif + PKeySignOptions sig_opt; + ErlNifBinary sig_bin; /* signature */ + unsigned char *tbs; /* data to be signed */ + size_t tbslen; + +#ifndef HAS_ENGINE_SUPPORT + if (enif_is_map(env, argv[4])) { + return atom_notsup; + } +#endif + + if (!enif_inspect_binary(env, argv[3], &sig_bin)) { + return enif_make_badarg(env); + } + + i = get_pkey_sign_digest(env, argv[0], argv[1], argv[2], md_value, &md, &tbs, &tbslen); + if (i != PKEY_OK) { + if (i == PKEY_NOTSUP) + return atom_notsup; + else + return enif_make_badarg(env); + } + + i = get_pkey_sign_options(env, argv[0], argv[5], md, &sig_opt); + if (i != PKEY_OK) { + if (i == PKEY_NOTSUP) + return atom_notsup; + else + return enif_make_badarg(env); + } + + if (get_pkey_public_key(env, argv[0], argv[4], &pkey) != PKEY_OK) { + return enif_make_badarg(env); + } + +#ifdef HAS_EVP_PKEY_CTX +/* printf("EVP interface\r\n"); + */ + ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) goto badarg; + + if (argv[0] != atom_eddsa) { + if (EVP_PKEY_verify_init(ctx) <= 0) goto badarg; + if (md != NULL && EVP_PKEY_CTX_set_signature_md(ctx, md) <= 0) goto badarg; + } + + if (argv[0] == atom_rsa) { + if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg; + if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) { + if (sig_opt.rsa_mgf1_md != NULL) { +# ifdef HAVE_RSA_MGF1_MD + if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg; +# else + EVP_PKEY_CTX_free(ctx); + EVP_PKEY_free(pkey); + return atom_notsup; +# endif + } + if (sig_opt.rsa_pss_saltlen > -2 + && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0) + goto badarg; + } + } + + if (argv[0] == atom_eddsa) { +#ifdef HAVE_EDDSA + EVP_MD_CTX* mdctx = EVP_MD_CTX_create(); + + if (!EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey)) { + if (mdctx) EVP_MD_CTX_destroy(mdctx); + goto badarg; + } + + i = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen); + EVP_MD_CTX_destroy(mdctx); +#else + goto badarg; +#endif + } + else + { + if (md != NULL) { + ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md)); + } + i = EVP_PKEY_verify(ctx, sig_bin.data, sig_bin.size, tbs, tbslen); + } + + EVP_PKEY_CTX_free(ctx); +#else +/*printf("Old interface\r\n"); +*/ + if (argv[0] == atom_rsa) { + RSA *rsa = EVP_PKEY_get1_RSA(pkey); + i = RSA_verify(md->type, tbs, tbslen, sig_bin.data, sig_bin.size, rsa); + RSA_free(rsa); + } else if (argv[0] == atom_dss) { + DSA *dsa = EVP_PKEY_get1_DSA(pkey); + i = DSA_verify(0, tbs, tbslen, sig_bin.data, sig_bin.size, dsa); + DSA_free(dsa); + } else if (argv[0] == atom_ecdsa) { +#if defined(HAVE_EC) + EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); + i = ECDSA_verify(EVP_MD_type(md), tbs, tbslen, sig_bin.data, sig_bin.size, ec); + EC_KEY_free(ec); +#else + EVP_PKEY_free(pkey); + return atom_notsup; +#endif + } else { + goto badarg; + } +#endif + + EVP_PKEY_free(pkey); + if (i == 1) { + return atom_true; + } else { + return atom_false; + } + + badarg: +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX_free(ctx); +#endif + EVP_PKEY_free(pkey); + return enif_make_badarg(env); +} + +static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_TERM options, + PKeyCryptOptions *opt) +{ + ERL_NIF_TERM head, tail; + const ERL_NIF_TERM *tpl_terms; + int tpl_arity; + const EVP_MD *opt_md; + int i; + + if (!enif_is_list(env, options)) { + return PKEY_BADARG; + } + + /* defaults */ + if (algorithm == atom_rsa) { + opt->rsa_mgf1_md = NULL; + opt->rsa_oaep_label.data = NULL; + opt->rsa_oaep_label.size = 0; + opt->rsa_oaep_md = NULL; + opt->rsa_padding = RSA_PKCS1_PADDING; + opt->signature_md = NULL; + } + + if (enif_is_empty_list(env, options)) { + return PKEY_OK; + } + + if (algorithm == atom_rsa) { + tail = options; + while (enif_get_list_cell(env, tail, &head, &tail)) { + if (enif_get_tuple(env, head, &tpl_arity, &tpl_terms) && tpl_arity == 2) { + if (tpl_terms[0] == atom_rsa_padding + || tpl_terms[0] == atom_rsa_pad /* Compatibility */ + ) { + if (tpl_terms[1] == atom_rsa_pkcs1_padding) { + opt->rsa_padding = RSA_PKCS1_PADDING; +#ifdef HAVE_RSA_OAEP_PADDING + } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) { + opt->rsa_padding = RSA_PKCS1_OAEP_PADDING; +#endif +#ifdef HAVE_RSA_SSLV23_PADDING + } else if (tpl_terms[1] == atom_rsa_sslv23_padding) { + opt->rsa_padding = RSA_SSLV23_PADDING; +#endif + } else if (tpl_terms[1] == atom_rsa_x931_padding) { + opt->rsa_padding = RSA_X931_PADDING; + } else if (tpl_terms[1] == atom_rsa_no_padding) { + opt->rsa_padding = RSA_NO_PADDING; + } else { + return PKEY_BADARG; + } + } else if (tpl_terms[0] == atom_signature_md && enif_is_atom(env, tpl_terms[1])) { + i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); + if (i != PKEY_OK) { + return i; + } + opt->signature_md = opt_md; + } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) { +#ifndef HAVE_RSA_MGF1_MD + if (tpl_terms[1] != atom_sha) + return PKEY_NOTSUP; +#endif + i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); + if (i != PKEY_OK) { + return i; + } + opt->rsa_mgf1_md = opt_md; + } else if (tpl_terms[0] == atom_rsa_oaep_label + && enif_inspect_binary(env, tpl_terms[1], &(opt->rsa_oaep_label))) { +#ifdef HAVE_RSA_OAEP_MD + continue; +#else + return PKEY_NOTSUP; +#endif + } else if (tpl_terms[0] == atom_rsa_oaep_md && enif_is_atom(env, tpl_terms[1])) { +#ifndef HAVE_RSA_OAEP_MD + if (tpl_terms[1] != atom_sha) + return PKEY_NOTSUP; +#endif + i = get_pkey_digest_type(env, algorithm, tpl_terms[1], &opt_md); + if (i != PKEY_OK) { + return i; + } + opt->rsa_oaep_md = opt_md; + } else { + return PKEY_BADARG; + } + } else { + return PKEY_BADARG; + } + } + } else { + return PKEY_BADARG; + } + + return PKEY_OK; +} + +static size_t size_of_RSA(EVP_PKEY *pkey) { + size_t tmplen; + RSA *rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa == NULL) return 0; + tmplen = RSA_size(rsa); + RSA_free(rsa); + return tmplen; +} + +ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) +{/* (Algorithm, Data, PublKey=[E,N]|[E,N,D]|[E,N,D,P1,P2,E1,E2,C], Options, IsPrivate, IsEncrypt) */ + int i; + EVP_PKEY *pkey; +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX *ctx; +#else + RSA *rsa; +#endif + PKeyCryptOptions crypt_opt; + ErlNifBinary in_bin, out_bin, tmp_bin; + size_t outlen; +#ifdef HAVE_RSA_SSLV23_PADDING + size_t tmplen; +#endif + int is_private = (argv[4] == atom_true), + is_encrypt = (argv[5] == atom_true); + int algo_init = 0; + +/* char algo[1024]; */ + +#ifndef HAS_ENGINE_SUPPORT + if (enif_is_map(env, argv[2])) { + return atom_notsup; + } +#endif + + if (!enif_inspect_binary(env, argv[1], &in_bin)) { + return enif_make_badarg(env); + } + + i = get_pkey_crypt_options(env, argv[0], argv[3], &crypt_opt); + if (i != PKEY_OK) { + if (i == PKEY_NOTSUP) + return atom_notsup; + else + return enif_make_badarg(env); + } + + if (is_private) { + if (get_pkey_private_key(env, argv[0], argv[2], &pkey) != PKEY_OK) { + return enif_make_badarg(env); + } + } else { + if (get_pkey_public_key(env, argv[0], argv[2], &pkey) != PKEY_OK) { + return enif_make_badarg(env); + } + } + + out_bin.data = NULL; + out_bin.size = 0; + tmp_bin.data = NULL; + tmp_bin.size = 0; + +#ifdef HAS_EVP_PKEY_CTX + ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) goto badarg; + +/* enif_get_atom(env,argv[0],algo,1024,ERL_NIF_LATIN1); */ + + if (is_private) { + if (is_encrypt) { + /* private encrypt */ + if ((algo_init=EVP_PKEY_sign_init(ctx)) <= 0) { + /* fprintf(stderr,"BADARG %s private encrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */ + goto badarg; + } + } else { + /* private decrypt */ + if ((algo_init=EVP_PKEY_decrypt_init(ctx)) <= 0) { + /* fprintf(stderr,"BADARG %s private decrypt algo_init=%d %s:%d\r\n", algo, algo_init, __FILE__, __LINE__); */ + goto badarg; + } + } + } else { + if (is_encrypt) { + /* public encrypt */ + if ((algo_init=EVP_PKEY_encrypt_init(ctx)) <= 0) { + /* fprintf(stderr,"BADARG %s public encrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */ + goto badarg; + } + } else { + /* public decrypt */ + if ((algo_init=EVP_PKEY_verify_recover_init(ctx)) <= 0) { + /* fprintf(stderr,"BADARG %s public decrypt algo_init=%d %s:%d\r\n", algo,algo_init,__FILE__, __LINE__); */ + goto badarg; + } + } + } + + if (argv[0] == atom_rsa) { + if (crypt_opt.signature_md != NULL + && EVP_PKEY_CTX_set_signature_md(ctx, crypt_opt.signature_md) <= 0) + goto badarg; +#ifdef HAVE_RSA_SSLV23_PADDING + if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) { + if (is_encrypt) { + tmplen = size_of_RSA(pkey); + if (tmplen == 0) goto badarg; + if (!enif_alloc_binary(tmplen, &tmp_bin)) goto badarg; + if (RSA_padding_add_SSLv23(tmp_bin.data, tmplen, in_bin.data, in_bin.size) <= 0) + goto badarg; + in_bin = tmp_bin; + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_NO_PADDING) <= 0) goto badarg; + } else +#endif + { + if (EVP_PKEY_CTX_set_rsa_padding(ctx, crypt_opt.rsa_padding) <= 0) goto badarg; + } +#ifdef HAVE_RSA_OAEP_MD + if (crypt_opt.rsa_padding == RSA_PKCS1_OAEP_PADDING) { + if (crypt_opt.rsa_oaep_md != NULL + && EVP_PKEY_CTX_set_rsa_oaep_md(ctx, crypt_opt.rsa_oaep_md) <= 0) + goto badarg; + if (crypt_opt.rsa_mgf1_md != NULL + && EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, crypt_opt.rsa_mgf1_md) <= 0) goto badarg; + if (crypt_opt.rsa_oaep_label.data != NULL && crypt_opt.rsa_oaep_label.size > 0) { + unsigned char *label_copy = NULL; + label_copy = OPENSSL_malloc(crypt_opt.rsa_oaep_label.size); + if (label_copy == NULL) goto badarg; + memcpy((void *)(label_copy), (const void *)(crypt_opt.rsa_oaep_label.data), + crypt_opt.rsa_oaep_label.size); + if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, label_copy, + crypt_opt.rsa_oaep_label.size) <= 0) { + OPENSSL_free(label_copy); + label_copy = NULL; + goto badarg; + } + } + } +#endif + } + + if (is_private) { + if (is_encrypt) { + /* private_encrypt */ + i = EVP_PKEY_sign(ctx, NULL, &outlen, in_bin.data, in_bin.size); + } else { + /* private_decrypt */ + i = EVP_PKEY_decrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size); + } + } else { + if (is_encrypt) { + /* public_encrypt */ + i = EVP_PKEY_encrypt(ctx, NULL, &outlen, in_bin.data, in_bin.size); + } else { + /* public_decrypt */ + i = EVP_PKEY_verify_recover(ctx, NULL, &outlen, in_bin.data, in_bin.size); + } + } + /* fprintf(stderr,"i = %d %s:%d\r\n", i, __FILE__, __LINE__); */ + + if (i != 1) goto badarg; + + enif_alloc_binary(outlen, &out_bin); + + if (is_private) { + if (is_encrypt) { + /* private_encrypt */ + i = EVP_PKEY_sign(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); + } else { + /* private_decrypt */ + i = EVP_PKEY_decrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); + } + } else { + if (is_encrypt) { + /* public_encrypt */ + i = EVP_PKEY_encrypt(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); + } else { + /* public_decrypt */ + i = EVP_PKEY_verify_recover(ctx, out_bin.data, &outlen, in_bin.data, in_bin.size); + } + } + +#else + /* Non-EVP cryptolib. Only support RSA */ + + if (argv[0] != atom_rsa) { + algo_init = -2; /* exitcode: notsup */ + goto badarg; + } + rsa = EVP_PKEY_get1_RSA(pkey); + enif_alloc_binary(RSA_size(rsa), &out_bin); + + if (is_private) { + if (is_encrypt) { + /* non-evp rsa private encrypt */ + ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size); + i = RSA_private_encrypt(in_bin.size, in_bin.data, + out_bin.data, rsa, crypt_opt.rsa_padding); + if (i > 0) { + ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); + } + } else { + /* non-evp rsa private decrypt */ + i = RSA_private_decrypt(in_bin.size, in_bin.data, + out_bin.data, rsa, crypt_opt.rsa_padding); + if (i > 0) { + ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); + enif_realloc_binary(&out_bin, i); + } + } + } else { + if (is_encrypt) { + /* non-evp rsa public encrypt */ + ERL_VALGRIND_ASSERT_MEM_DEFINED(in_bin.data,in_bin.size); + i = RSA_public_encrypt(in_bin.size, in_bin.data, + out_bin.data, rsa, crypt_opt.rsa_padding); + if (i > 0) { + ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); + } + } else { + /* non-evp rsa public decrypt */ + i = RSA_public_decrypt(in_bin.size, in_bin.data, + out_bin.data, rsa, crypt_opt.rsa_padding); + if (i > 0) { + ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, i); + enif_realloc_binary(&out_bin, i); + } + } + } + + outlen = i; + RSA_free(rsa); +#endif + + if ((i > 0) && argv[0] == atom_rsa && !is_encrypt) { +#ifdef HAVE_RSA_SSLV23_PADDING + if (crypt_opt.rsa_padding == RSA_SSLV23_PADDING) { + unsigned char *p; + tmplen = size_of_RSA(pkey); + if (tmplen == 0) goto badarg; + if (!enif_alloc_binary(tmplen, &tmp_bin)) + goto badarg; + p = out_bin.data; + p++; + i = RSA_padding_check_SSLv23(tmp_bin.data, tmplen, p, out_bin.size - 1, tmplen); + if (i >= 0) { + outlen = i; + in_bin = out_bin; + out_bin = tmp_bin; + tmp_bin = in_bin; + i = 1; + } + } +#endif + } + + if (tmp_bin.data != NULL) { + enif_release_binary(&tmp_bin); + } + +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX_free(ctx); +#else +#endif + EVP_PKEY_free(pkey); + if (i > 0) { + ERL_VALGRIND_MAKE_MEM_DEFINED(out_bin.data, outlen); + if (outlen != out_bin.size) { + enif_realloc_binary(&out_bin, outlen); + ERL_VALGRIND_ASSERT_MEM_DEFINED(out_bin.data, outlen); + } + return enif_make_binary(env, &out_bin); + } else { + enif_release_binary(&out_bin); + return atom_error; + } + + badarg: + if (out_bin.data != NULL) { + enif_release_binary(&out_bin); + } + if (tmp_bin.data != NULL) { + enif_release_binary(&tmp_bin); + } +#ifdef HAS_EVP_PKEY_CTX + EVP_PKEY_CTX_free(ctx); +#else +#endif + EVP_PKEY_free(pkey); + if (algo_init == -2) + return atom_notsup; + else + return enif_make_badarg(env); +} + +ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ /* (Algorithm, PrivKey | KeyMap) */ + EVP_PKEY *pkey; + ERL_NIF_TERM alg = argv[0]; + ERL_NIF_TERM result[8]; + if (get_pkey_private_key(env, alg, argv[1], &pkey) != PKEY_OK) { + return enif_make_badarg(env); + } + + if (alg == atom_rsa) { + const BIGNUM *n = NULL, *e = NULL, *d = NULL; + RSA *rsa = EVP_PKEY_get1_RSA(pkey); + if (rsa) { + RSA_get0_key(rsa, &n, &e, &d); + result[0] = bin_from_bn(env, e); // Exponent E + result[1] = bin_from_bn(env, n); // Modulus N = p*q + RSA_free(rsa); + EVP_PKEY_free(pkey); + return enif_make_list_from_array(env, result, 2); + } + + } else if (argv[0] == atom_dss) { + const BIGNUM *p = NULL, *q = NULL, *g = NULL, *pub_key = NULL; + DSA *dsa = EVP_PKEY_get1_DSA(pkey); + if (dsa) { + DSA_get0_pqg(dsa, &p, &q, &g); + DSA_get0_key(dsa, &pub_key, NULL); + result[0] = bin_from_bn(env, p); + result[1] = bin_from_bn(env, q); + result[2] = bin_from_bn(env, g); + result[3] = bin_from_bn(env, pub_key); + DSA_free(dsa); + EVP_PKEY_free(pkey); + return enif_make_list_from_array(env, result, 4); + } + + } else if (argv[0] == atom_ecdsa) { +#if defined(HAVE_EC) + /* not yet implemented + EC_KEY *ec = EVP_PKEY_get1_EC_KEY(pkey); + if (ec) { + / * Example of result: + { + Curve = {Field, Prime, Point, Order, CoFactor} = + { + Field = {prime_field,<<255,...,255>>}, + Prime = {<<255,...,252>>, + <<90,...,75>>, + <<196,...,144>> + }, + Point = <<4,...,245>>, + Order = <<255,...,81>>, + CoFactor = <<1>> + }, + Key = <<151,...,62>> + } + or + { + Curve = + {characteristic_two_field, + M, + Basis = {tpbasis, _} + | {ppbasis, k1, k2, k3} + }, + Key + } + * / + EVP_PKEY_free(pkey); + return enif_make_list_from_array(env, ..., ...); + */ +#endif + } + + if (pkey) EVP_PKEY_free(pkey); + return enif_make_badarg(env); +} diff --git a/lib/crypto/c_src/pkey.h b/lib/crypto/c_src/pkey.h new file mode 100644 index 0000000000..f647a4a160 --- /dev/null +++ b/lib/crypto/c_src/pkey.h @@ -0,0 +1,31 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_PKEY_H__ +#define E_PKEY_H__ 1 + +#include "common.h" + +ERL_NIF_TERM pkey_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM pkey_verify_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM pkey_crypt_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM privkey_to_pubkey_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_PKEY_H__ */ diff --git a/lib/crypto/c_src/poly1305.c b/lib/crypto/c_src/poly1305.c new file mode 100644 index 0000000000..3e2bcfa60e --- /dev/null +++ b/lib/crypto/c_src/poly1305.c @@ -0,0 +1,78 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "poly1305.h" + +/* For OpenSSL >= 1.1.1 the hmac_nif and cmac_nif could be integrated into poly1305 (with 'type' as parameter) */ +ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key, Text) */ +#ifdef HAVE_POLY1305 + ErlNifBinary key_bin, text, ret_bin; + ERL_NIF_TERM ret = atom_error; + EVP_PKEY *key = NULL; + EVP_MD_CTX *mctx = NULL; + EVP_PKEY_CTX *pctx = NULL; + const EVP_MD *md = NULL; + size_t size; + int type; + + type = EVP_PKEY_POLY1305; + + if (!enif_inspect_binary(env, argv[0], &key_bin) || + !(key_bin.size == 32) ) { + return enif_make_badarg(env); + } + + if (!enif_inspect_binary(env, argv[1], &text) ) { + return enif_make_badarg(env); + } + + key = EVP_PKEY_new_raw_private_key(type, /*engine*/ NULL, key_bin.data, key_bin.size); + + if (!key || + !(mctx = EVP_MD_CTX_new()) || + !EVP_DigestSignInit(mctx, &pctx, md, /*engine*/ NULL, key) || + !EVP_DigestSignUpdate(mctx, text.data, text.size)) { + goto err; + } + + if (!EVP_DigestSignFinal(mctx, NULL, &size) || + !enif_alloc_binary(size, &ret_bin) || + !EVP_DigestSignFinal(mctx, ret_bin.data, &size)) { + goto err; + } + + if ((size != ret_bin.size) && + !enif_realloc_binary(&ret_bin, size)) { + goto err; + } + + ret = enif_make_binary(env, &ret_bin); + + err: + EVP_MD_CTX_free(mctx); + EVP_PKEY_free(key); + return ret; + +#else + return atom_notsup; +#endif +} + diff --git a/lib/crypto/c_src/poly1305.h b/lib/crypto/c_src/poly1305.h new file mode 100644 index 0000000000..4bf45e6218 --- /dev/null +++ b/lib/crypto/c_src/poly1305.h @@ -0,0 +1,28 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_POLY1305_H__ +#define E_POLY1305_H__ 1 + +#include "common.h" + +ERL_NIF_TERM poly1305_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_POLY1305_H__ */ diff --git a/lib/crypto/c_src/rand.c b/lib/crypto/c_src/rand.c new file mode 100644 index 0000000000..e71e202f36 --- /dev/null +++ b/lib/crypto/c_src/rand.c @@ -0,0 +1,99 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "rand.h" +#include "bn.h" + +ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Bytes) */ + unsigned bytes; + unsigned char* data; + ERL_NIF_TERM ret; + + if (!enif_get_uint(env, argv[0], &bytes)) { + return enif_make_badarg(env); + } + data = enif_make_new_binary(env, bytes, &ret); + if ( RAND_bytes(data, bytes) != 1) { + return atom_false; + } + ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes); + return ret; +} + +ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Range) */ + BIGNUM *bn_range, *bn_rand; + ERL_NIF_TERM ret; + + if(!get_bn_from_bin(env, argv[0], &bn_range)) { + return enif_make_badarg(env); + } + + bn_rand = BN_new(); + if (BN_rand_range(bn_rand, bn_range) != 1) { + ret = atom_false; + } + else { + ret = bin_from_bn(env, bn_rand); + } + BN_free(bn_rand); + BN_free(bn_range); + return ret; +} + +ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Lo,Hi) */ + BIGNUM *bn_from = NULL, *bn_to, *bn_rand; + unsigned char* data; + unsigned dlen; + ERL_NIF_TERM ret; + + if (!get_bn_from_mpint(env, argv[0], &bn_from) + || !get_bn_from_mpint(env, argv[1], &bn_rand)) { + if (bn_from) BN_free(bn_from); + return enif_make_badarg(env); + } + + bn_to = BN_new(); + BN_sub(bn_to, bn_rand, bn_from); + BN_pseudo_rand_range(bn_rand, bn_to); + BN_add(bn_rand, bn_rand, bn_from); + dlen = BN_num_bytes(bn_rand); + data = enif_make_new_binary(env, dlen+4, &ret); + put_int32(data, dlen); + BN_bn2bin(bn_rand, data+4); + ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen); + BN_free(bn_rand); + BN_free(bn_from); + BN_free(bn_to); + return ret; +} + +ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + ErlNifBinary seed_bin; + + if (!enif_inspect_binary(env, argv[0], &seed_bin)) + return enif_make_badarg(env); + RAND_seed(seed_bin.data,seed_bin.size); + return atom_ok; +} + diff --git a/lib/crypto/c_src/rand.h b/lib/crypto/c_src/rand.h new file mode 100644 index 0000000000..9c23d343ec --- /dev/null +++ b/lib/crypto/c_src/rand.h @@ -0,0 +1,31 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_RAND_H__ +#define E_RAND_H__ 1 + +#include "common.h" + +ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM strong_rand_range_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM rand_seed_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_RAND_H__ */ diff --git a/lib/crypto/c_src/rc4.c b/lib/crypto/c_src/rc4.c new file mode 100644 index 0000000000..483c87b04b --- /dev/null +++ b/lib/crypto/c_src/rc4.c @@ -0,0 +1,66 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "rc4.h" + +ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Key) */ +#ifndef OPENSSL_NO_RC4 + ErlNifBinary key; + ERL_NIF_TERM ret; + + CHECK_NO_FIPS_MODE(); + + if (!enif_inspect_iolist_as_binary(env,argv[0], &key)) { + return enif_make_badarg(env); + } + RC4_set_key((RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret), + key.size, key.data); + return ret; +#else + return enif_raise_exception(env, atom_notsup); +#endif +} + +ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (State, Data) */ +#ifndef OPENSSL_NO_RC4 + ErlNifBinary state, data; + RC4_KEY* rc4_key; + ERL_NIF_TERM new_state, new_data; + + CHECK_NO_FIPS_MODE(); + + if (!enif_inspect_iolist_as_binary(env,argv[0], &state) + || state.size != sizeof(RC4_KEY) + || !enif_inspect_iolist_as_binary(env,argv[1], &data)) { + return enif_make_badarg(env); + } + rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state); + memcpy(rc4_key, state.data, sizeof(RC4_KEY)); + RC4(rc4_key, data.size, data.data, + enif_make_new_binary(env, data.size, &new_data)); + CONSUME_REDS(env,data); + return enif_make_tuple2(env,new_state,new_data); +#else + return enif_raise_exception(env, atom_notsup); +#endif +} + diff --git a/lib/crypto/c_src/rc4.h b/lib/crypto/c_src/rc4.h new file mode 100644 index 0000000000..28bf674253 --- /dev/null +++ b/lib/crypto/c_src/rc4.h @@ -0,0 +1,29 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_RC4_H__ +#define E_RC4_H__ 1 + +#include "common.h" + +ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_RC4_H__ */ diff --git a/lib/crypto/c_src/rsa.c b/lib/crypto/c_src/rsa.c new file mode 100644 index 0000000000..92867671fb --- /dev/null +++ b/lib/crypto/c_src/rsa.c @@ -0,0 +1,195 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "rsa.h" +#include "bn.h" + +static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa); +static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt); + +int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa) +{ + /* key=[E,N,D]|[E,N,D,P1,P2,E1,E2,C] */ + ERL_NIF_TERM head, tail; + BIGNUM *e, *n, *d; + BIGNUM *p, *q; + BIGNUM *dmp1, *dmq1, *iqmp; + + if (!enif_get_list_cell(env, key, &head, &tail) + || !get_bn_from_bin(env, head, &e) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &n) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &d)) { + return 0; + } + (void) RSA_set0_key(rsa, n, e, d); + if (enif_is_empty_list(env, tail)) { + return 1; + } + if (!enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &p) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &q) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dmp1) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &dmq1) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &iqmp) + || !enif_is_empty_list(env, tail)) { + return 0; + } + (void) RSA_set0_factors(rsa, p, q); + (void) RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp); + return 1; +} + +int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa) +{ + /* key=[E,N] */ + ERL_NIF_TERM head, tail; + BIGNUM *e, *n; + + if (!enif_get_list_cell(env, key, &head, &tail) + || !get_bn_from_bin(env, head, &e) + || !enif_get_list_cell(env, tail, &head, &tail) + || !get_bn_from_bin(env, head, &n) + || !enif_is_empty_list(env, tail)) { + return 0; + } + + (void) RSA_set0_key(rsa, n, e, NULL); + return 1; +} + +/* Creates a term which can be parsed by get_rsa_private_key(). This is a list of plain integer binaries (not mpints). */ +static ERL_NIF_TERM put_rsa_private_key(ErlNifEnv* env, const RSA *rsa) +{ + ERL_NIF_TERM result[8]; + const BIGNUM *n, *e, *d, *p, *q, *dmp1, *dmq1, *iqmp; + + /* Return at least [E,N,D] */ + n = NULL; e = NULL; d = NULL; + RSA_get0_key(rsa, &n, &e, &d); + + result[0] = bin_from_bn(env, e); // Exponent E + result[1] = bin_from_bn(env, n); // Modulus N = p*q + result[2] = bin_from_bn(env, d); // Exponent D + + /* Check whether the optional additional parameters are available */ + p = NULL; q = NULL; + RSA_get0_factors(rsa, &p, &q); + dmp1 = NULL; dmq1 = NULL; iqmp = NULL; + RSA_get0_crt_params(rsa, &dmp1, &dmq1, &iqmp); + + if (p && q && dmp1 && dmq1 && iqmp) { + result[3] = bin_from_bn(env, p); // Factor p + result[4] = bin_from_bn(env, q); // Factor q + result[5] = bin_from_bn(env, dmp1); // D mod (p-1) + result[6] = bin_from_bn(env, dmq1); // D mod (q-1) + result[7] = bin_from_bn(env, iqmp); // (1/q) mod p + + return enif_make_list_from_array(env, result, 8); + } else { + return enif_make_list_from_array(env, result, 3); + } +} + +static int check_erlang_interrupt(int maj, int min, BN_GENCB *ctxt) +{ + ErlNifEnv *env = BN_GENCB_get_arg(ctxt); + + if (!enif_is_current_process_alive(env)) { + return 0; + } else { + return 1; + } +} + +static ERL_NIF_TERM rsa_generate_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (ModulusSize, PublicExponent) */ + int modulus_bits; + BIGNUM *pub_exp, *three; + RSA *rsa; + int success; + ERL_NIF_TERM result; + BN_GENCB *intr_cb; +#ifndef HAVE_OPAQUE_BN_GENCB + BN_GENCB intr_cb_buf; +#endif + + if (!enif_get_int(env, argv[0], &modulus_bits) || modulus_bits < 256) { + return enif_make_badarg(env); + } + + if (!get_bn_from_bin(env, argv[1], &pub_exp)) { + return enif_make_badarg(env); + } + + /* Make sure the public exponent is large enough (at least 3). + * Without this, RSA_generate_key_ex() can run forever. */ + three = BN_new(); + BN_set_word(three, 3); + success = BN_cmp(pub_exp, three); + BN_free(three); + if (success < 0) { + BN_free(pub_exp); + return enif_make_badarg(env); + } + + /* For large keys, prime generation can take many seconds. Set up + * the callback which we use to test whether the process has been + * interrupted. */ +#ifdef HAVE_OPAQUE_BN_GENCB + intr_cb = BN_GENCB_new(); +#else + intr_cb = &intr_cb_buf; +#endif + BN_GENCB_set(intr_cb, check_erlang_interrupt, env); + + rsa = RSA_new(); + success = RSA_generate_key_ex(rsa, modulus_bits, pub_exp, intr_cb); + BN_free(pub_exp); + +#ifdef HAVE_OPAQUE_BN_GENCB + BN_GENCB_free(intr_cb); +#endif + + if (!success) { + RSA_free(rsa); + return atom_error; + } + + result = put_rsa_private_key(env, rsa); + RSA_free(rsa); + + return result; +} + +ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{ + /* RSA key generation can take a long time (>1 sec for a large + * modulus), so schedule it as a CPU-bound operation. */ + return enif_schedule_nif(env, "rsa_generate_key", + ERL_NIF_DIRTY_JOB_CPU_BOUND, + rsa_generate_key, argc, argv); +} diff --git a/lib/crypto/c_src/rsa.h b/lib/crypto/c_src/rsa.h new file mode 100644 index 0000000000..69c02aa2cb --- /dev/null +++ b/lib/crypto/c_src/rsa.h @@ -0,0 +1,31 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_RSA_H__ +#define E_RSA_H__ 1 + +#include "common.h" + +int get_rsa_public_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa); +int get_rsa_private_key(ErlNifEnv* env, ERL_NIF_TERM key, RSA *rsa); + +ERL_NIF_TERM rsa_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_RSA_H__ */ diff --git a/lib/crypto/c_src/srp.c b/lib/crypto/c_src/srp.c new file mode 100644 index 0000000000..1552bc8cc1 --- /dev/null +++ b/lib/crypto/c_src/srp.c @@ -0,0 +1,229 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#include "srp.h" +#include "bn.h" + +ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Multiplier, Verifier, Generator, Exponent, Prime) */ + BIGNUM *bn_verifier = NULL; + BIGNUM *bn_exponent = NULL, *bn_generator = NULL, *bn_prime = NULL, *bn_multiplier = NULL, *bn_result; + BN_CTX *bn_ctx; + unsigned char* ptr; + unsigned dlen; + ERL_NIF_TERM ret; + + CHECK_NO_FIPS_MODE(); + + if (!get_bn_from_bin(env, argv[0], &bn_multiplier) + || !get_bn_from_bin(env, argv[1], &bn_verifier) + || !get_bn_from_bin(env, argv[2], &bn_generator) + || !get_bn_from_bin(env, argv[3], &bn_exponent) + || !get_bn_from_bin(env, argv[4], &bn_prime)) { + if (bn_multiplier) BN_free(bn_multiplier); + if (bn_verifier) BN_free(bn_verifier); + if (bn_generator) BN_free(bn_generator); + if (bn_exponent) BN_free(bn_exponent); + if (bn_prime) BN_free(bn_prime); + return enif_make_badarg(env); + } + + bn_result = BN_new(); + bn_ctx = BN_CTX_new(); + + /* B = k*v + g^b % N */ + + /* k * v */ + BN_mod_mul(bn_multiplier, bn_multiplier, bn_verifier, bn_prime, bn_ctx); + + /* g^b % N */ + BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx); + + /* k*v + g^b % N */ + BN_mod_add(bn_result, bn_result, bn_multiplier, bn_prime, bn_ctx); + + /* check that B % N != 0, reuse bn_multiplier */ + BN_nnmod(bn_multiplier, bn_result, bn_prime, bn_ctx); + if (BN_is_zero(bn_multiplier)) { + ret = atom_error; + } else { + dlen = BN_num_bytes(bn_result); + ptr = enif_make_new_binary(env, dlen, &ret); + BN_bn2bin(bn_result, ptr); + } + BN_free(bn_result); + BN_CTX_free(bn_ctx); + BN_free(bn_prime); + BN_free(bn_generator); + BN_free(bn_multiplier); + BN_free(bn_exponent); + BN_free(bn_verifier); + return ret; +} + +ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (a, u, B, Multiplier, Prime, Exponent, Generator) */ +/* + <premaster secret> = (B - (k * g^x)) ^ (a + (u * x)) % N +*/ + BIGNUM *bn_exponent = NULL, *bn_a = NULL; + BIGNUM *bn_u = NULL, *bn_multiplier = NULL, *bn_exp2, + *bn_base, *bn_prime = NULL, *bn_generator = NULL, + *bn_B = NULL, *bn_result; + BN_CTX *bn_ctx; + unsigned char* ptr; + unsigned dlen; + ERL_NIF_TERM ret; + + CHECK_NO_FIPS_MODE(); + + if (!get_bn_from_bin(env, argv[0], &bn_a) + || !get_bn_from_bin(env, argv[1], &bn_u) + || !get_bn_from_bin(env, argv[2], &bn_B) + || !get_bn_from_bin(env, argv[3], &bn_multiplier) + || !get_bn_from_bin(env, argv[4], &bn_generator) + || !get_bn_from_bin(env, argv[5], &bn_exponent) + || !get_bn_from_bin(env, argv[6], &bn_prime)) + { + if (bn_exponent) BN_free(bn_exponent); + if (bn_a) BN_free(bn_a); + if (bn_u) BN_free(bn_u); + if (bn_B) BN_free(bn_B); + if (bn_multiplier) BN_free(bn_multiplier); + if (bn_generator) BN_free(bn_generator); + if (bn_prime) BN_free(bn_prime); + return enif_make_badarg(env); + } + + bn_ctx = BN_CTX_new(); + bn_result = BN_new(); + + /* check that B % N != 0 */ + BN_nnmod(bn_result, bn_B, bn_prime, bn_ctx); + if (BN_is_zero(bn_result)) { + BN_free(bn_exponent); + BN_free(bn_a); + BN_free(bn_generator); + BN_free(bn_prime); + BN_free(bn_u); + BN_free(bn_B); + BN_CTX_free(bn_ctx); + + return atom_error; + } + + /* (B - (k * g^x)) */ + bn_base = BN_new(); + BN_mod_exp(bn_result, bn_generator, bn_exponent, bn_prime, bn_ctx); + BN_mod_mul(bn_result, bn_multiplier, bn_result, bn_prime, bn_ctx); + BN_mod_sub(bn_base, bn_B, bn_result, bn_prime, bn_ctx); + + /* a + (u * x) */ + bn_exp2 = BN_new(); + BN_mul(bn_result, bn_u, bn_exponent, bn_ctx); + BN_add(bn_exp2, bn_a, bn_result); + + /* (B - (k * g^x)) ^ (a + (u * x)) % N */ + BN_mod_exp(bn_result, bn_base, bn_exp2, bn_prime, bn_ctx); + + dlen = BN_num_bytes(bn_result); + ptr = enif_make_new_binary(env, dlen, &ret); + BN_bn2bin(bn_result, ptr); + BN_free(bn_result); + BN_CTX_free(bn_ctx); + + BN_free(bn_multiplier); + BN_free(bn_exp2); + BN_free(bn_u); + BN_free(bn_exponent); + BN_free(bn_a); + BN_free(bn_B); + BN_free(bn_base); + BN_free(bn_generator); + BN_free(bn_prime); + return ret; +} + +ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) +{/* (Verifier, b, u, A, Prime) */ +/* + <premaster secret> = (A * v^u) ^ b % N +*/ + BIGNUM *bn_b = NULL, *bn_verifier = NULL; + BIGNUM *bn_prime = NULL, *bn_A = NULL, *bn_u = NULL, *bn_base, *bn_result; + BN_CTX *bn_ctx; + unsigned char* ptr; + unsigned dlen; + ERL_NIF_TERM ret; + + CHECK_NO_FIPS_MODE(); + + if (!get_bn_from_bin(env, argv[0], &bn_verifier) + || !get_bn_from_bin(env, argv[1], &bn_b) + || !get_bn_from_bin(env, argv[2], &bn_u) + || !get_bn_from_bin(env, argv[3], &bn_A) + || !get_bn_from_bin(env, argv[4], &bn_prime)) + { + if (bn_verifier) BN_free(bn_verifier); + if (bn_b) BN_free(bn_b); + if (bn_u) BN_free(bn_u); + if (bn_A) BN_free(bn_A); + if (bn_prime) BN_free(bn_prime); + return enif_make_badarg(env); + } + + bn_ctx = BN_CTX_new(); + bn_result = BN_new(); + + /* check that A % N != 0 */ + BN_nnmod(bn_result, bn_A, bn_prime, bn_ctx); + if (BN_is_zero(bn_result)) { + BN_free(bn_b); + BN_free(bn_verifier); + BN_free(bn_prime); + BN_free(bn_A); + BN_CTX_free(bn_ctx); + + return atom_error; + } + + /* (A * v^u) */ + bn_base = BN_new(); + BN_mod_exp(bn_base, bn_verifier, bn_u, bn_prime, bn_ctx); + BN_mod_mul(bn_base, bn_A, bn_base, bn_prime, bn_ctx); + + /* (A * v^u) ^ b % N */ + BN_mod_exp(bn_result, bn_base, bn_b, bn_prime, bn_ctx); + + dlen = BN_num_bytes(bn_result); + ptr = enif_make_new_binary(env, dlen, &ret); + BN_bn2bin(bn_result, ptr); + BN_free(bn_result); + BN_CTX_free(bn_ctx); + + BN_free(bn_u); + BN_free(bn_base); + BN_free(bn_verifier); + BN_free(bn_prime); + BN_free(bn_A); + BN_free(bn_b); + return ret; +} + diff --git a/lib/crypto/c_src/srp.h b/lib/crypto/c_src/srp.h new file mode 100644 index 0000000000..c356690470 --- /dev/null +++ b/lib/crypto/c_src/srp.h @@ -0,0 +1,30 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 2010-2018. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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% + */ + +#ifndef E_SRP_H__ +#define E_SRP_H__ 1 + +#include "common.h" + +ERL_NIF_TERM srp_value_B_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM srp_user_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); +ERL_NIF_TERM srp_host_secret_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); + +#endif /* E_SRP_H__ */ diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index 96fda34860..3306fe3d16 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -22,7 +22,7 @@ <title>crypto</title> </header> - <module>crypto</module> + <module since="">crypto</module> <modulesummary>Crypto Functions</modulesummary> <description> <p>This module provides a set of cryptographic functions. @@ -523,7 +523,7 @@ <!--================ FUNCTIONS ================--> <funcs> <func> - <name name="block_encrypt" arity="3"/> + <name name="block_encrypt" arity="3" since="OTP 18.0"/> <fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary> <desc> <p>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher.</p> @@ -536,7 +536,7 @@ </func> <func> - <name name="block_decrypt" arity="3"/> + <name name="block_decrypt" arity="3" since="OTP 18.0"/> <fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary> <desc> <p>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher.</p> @@ -549,9 +549,9 @@ </func> <func> - <name>block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name> - <name>block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name> - <name>block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag}</name> + <name since="OTP R16B01">block_encrypt(Type, Key, Ivec, PlainText) -> CipherText</name> + <name since="OTP R16B01">block_encrypt(AeadType, Key, Ivec, {AAD, PlainText}) -> {CipherText, CipherTag}</name> + <name since="OTP R16B01">block_encrypt(aes_gcm | aes_ccm, Key, Ivec, {AAD, PlainText, TagLength}) -> {CipherText, CipherTag}</name> <fsummary>Encrypt <c>PlainText</c> according to <c>Type</c> block cipher</fsummary> <type> <v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v> @@ -576,8 +576,8 @@ </func> <func> - <name>block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name> - <name>block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name> + <name since="OTP R16B01">block_decrypt(Type, Key, Ivec, CipherText) -> PlainText</name> + <name since="OTP R16B01">block_decrypt(AeadType, Key, Ivec, {AAD, CipherText, CipherTag}) -> PlainText | error</name> <fsummary>Decrypt <c>CipherText</c> according to <c>Type</c> block cipher</fsummary> <type> <v>Type = <seealso marker="#type-block_cipher_with_iv">block_cipher_with_iv()</seealso></v> @@ -602,7 +602,7 @@ </func> <func> - <name name="bytes_to_integer" arity="1"/> + <name name="bytes_to_integer" arity="1" since="OTP R16B01"/> <fsummary>Convert binary representation, of an integer, to an Erlang integer.</fsummary> <desc> <p>Convert binary representation, of an integer, to an Erlang integer. @@ -611,7 +611,7 @@ </func> <func> - <name name="compute_key" arity="4"/> + <name name="compute_key" arity="4" since="OTP R16B01"/> <fsummary>Computes the shared secret</fsummary> <desc> <p>Computes the shared secret from the private key and the other party's public key. @@ -621,7 +621,7 @@ </func> <func> - <name name="exor" arity="2"/> + <name name="exor" arity="2" since=""/> <fsummary>XOR data</fsummary> <desc> <p>Performs bit-wise XOR (exclusive or) on the data supplied.</p> @@ -630,8 +630,8 @@ <func> - <name name="generate_key" arity="2"/> - <name name="generate_key" arity="3"/> + <name name="generate_key" arity="2" since="OTP R16B01"/> + <name name="generate_key" arity="3" since="OTP R16B01"/> <fsummary>Generates a public key of type <c>Type</c></fsummary> <desc> <p>Generates a public key of type <c>Type</c>. @@ -652,7 +652,7 @@ </func> <func> - <name name="hash" arity="2"/> + <name name="hash" arity="2" since="OTP R15B02"/> <fsummary></fsummary> <desc> <p>Computes a message digest of type <c>Type</c> from <c>Data</c>.</p> @@ -662,7 +662,7 @@ </func> <func> - <name name="hash_init" arity="1"/> + <name name="hash_init" arity="1" since="OTP R15B02"/> <fsummary></fsummary> <desc> <p>Initializes the context for streaming hash operations. <c>Type</c> determines @@ -674,7 +674,7 @@ </func> <func> - <name name="hash_update" arity="2"/> + <name name="hash_update" arity="2" since="OTP R15B02"/> <fsummary></fsummary> <desc> <p>Updates the digest represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c> @@ -686,7 +686,7 @@ </func> <func> - <name name="hash_final" arity="1"/> + <name name="hash_final" arity="1" since="OTP R15B02"/> <fsummary></fsummary> <desc> <p>Finalizes the hash operation referenced by <c>Context</c> returned @@ -697,8 +697,8 @@ </func> <func> - <name name="hmac" arity="3"/> - <name name="hmac" arity="4"/> + <name name="hmac" arity="3" since="OTP R16B"/> + <name name="hmac" arity="4" since="OTP R16B"/> <fsummary></fsummary> <desc> <p>Computes a HMAC of type <c>Type</c> from <c>Data</c> using @@ -708,7 +708,7 @@ </func> <func> - <name name="hmac_init" arity="2"/> + <name name="hmac_init" arity="2" since="OTP R14B03"/> <fsummary></fsummary> <desc> <p>Initializes the context for streaming HMAC operations. <c>Type</c> determines @@ -718,7 +718,7 @@ </func> <func> - <name name="hmac_update" arity="2"/> + <name name="hmac_update" arity="2" since="OTP R14B03"/> <fsummary></fsummary> <desc> <p>Updates the HMAC represented by <c>Context</c> using the given <c>Data</c>. <c>Context</c> @@ -737,7 +737,7 @@ </func> <func> - <name name="hmac_final" arity="1"/> + <name name="hmac_final" arity="1" since="OTP R14B03"/> <fsummary></fsummary> <desc> <p>Finalizes the HMAC operation referenced by <c>Context</c>. The size of the resultant MAC is @@ -746,7 +746,7 @@ </func> <func> - <name name="hmac_final_n" arity="2"/> + <name name="hmac_final_n" arity="2" since="OTP R14B03"/> <fsummary></fsummary> <desc> <p>Finalizes the HMAC operation referenced by <c>Context</c>. <c>HashLen</c> must be greater than @@ -755,8 +755,8 @@ </func> <func> - <name name="cmac" arity="3"/> - <name name="cmac" arity="4"/> + <name name="cmac" arity="3" since="OTP 20.0"/> + <name name="cmac" arity="4" since="OTP 20.0"/> <fsummary>Calculates the Cipher-based Message Authentication Code.</fsummary> <desc> <p>Computes a CMAC of type <c>Type</c> from <c>Data</c> using @@ -766,7 +766,7 @@ </func> <func> - <name name="info_fips" arity="0"/> + <name name="info_fips" arity="0" since="OTP 20.0"/> <fsummary>Provides information about the FIPS operating status.</fsummary> <desc> <p>Provides information about the FIPS operating status of @@ -789,7 +789,7 @@ </func> <func> - <name name="enable_fips_mode" arity="1"/> + <name name="enable_fips_mode" arity="1" since="OTP 21.1"/> <fsummary>Change FIPS mode.</fsummary> <desc> <p>Enables (<c>Enable = true</c>) or disables (<c>Enable = false</c>) FIPS mode. Returns <c>true</c> if @@ -804,7 +804,7 @@ </func> <func> - <name name="info_lib" arity="0"/> + <name name="info_lib" arity="0" since=""/> <fsummary>Provides information about the libraries used by crypto.</fsummary> <desc> <p>Provides the name and version of the libraries used by crypto.</p> @@ -825,7 +825,7 @@ </func> <func> - <name name="mod_pow" arity="3"/> + <name name="mod_pow" arity="3" since="OTP R16B01"/> <fsummary>Computes the function: N^P mod M</fsummary> <desc> <p>Computes the function <c>N^P mod M</c>.</p> @@ -833,8 +833,8 @@ </func> <func> - <name name="next_iv" arity="2"/> - <name name="next_iv" arity="3"/> + <name name="next_iv" arity="2" since="OTP R16B01"/> + <name name="next_iv" arity="3" since="OTP R16B01"/> <fsummary></fsummary> <desc> <p>Returns the initialization vector to be used in the next @@ -846,7 +846,7 @@ </func> <func> - <name name="poly1305" arity="2"/> + <name name="poly1305" arity="2" since="OTP 21.1"/> <fsummary></fsummary> <desc> <p>Computes a POLY1305 message authentication code (<c>Mac</c>) from <c>Data</c> using @@ -855,7 +855,7 @@ </func> <func> - <name name="private_decrypt" arity="4"/> + <name name="private_decrypt" arity="4" since="OTP R16B01"/> <fsummary>Decrypts CipherText using the private Key.</fsummary> <desc> <p>Decrypts the <c>CipherText</c>, encrypted with @@ -869,7 +869,7 @@ </func> <func> - <name name="private_encrypt" arity="4"/> + <name name="private_encrypt" arity="4" since="OTP R16B01"/> <fsummary>Encrypts PlainText using the private Key.</fsummary> <desc> <p>Encrypts the <c>PlainText</c> using the <c>PrivateKey</c> @@ -882,7 +882,7 @@ </func> <func> - <name name="public_decrypt" arity="4"/> + <name name="public_decrypt" arity="4" since="OTP R16B01"/> <fsummary>Decrypts CipherText using the public Key.</fsummary> <desc> <p>Decrypts the <c>CipherText</c>, encrypted with @@ -896,7 +896,7 @@ </func> <func> - <name name="public_encrypt" arity="4"/> + <name name="public_encrypt" arity="4" since="OTP R16B01"/> <fsummary>Encrypts PlainText using the public Key.</fsummary> <desc> <p>Encrypts the <c>PlainText</c> (message digest) using the <c>PublicKey</c> @@ -908,7 +908,7 @@ </func> <func> - <name name="rand_seed" arity="1"/> + <name name="rand_seed" arity="1" since="OTP 17.0"/> <fsummary>Set the seed for random bytes generation</fsummary> <desc> <p>Set the seed for PRNG to the given binary. This calls the @@ -921,7 +921,7 @@ </func> <func> - <name>rand_uniform(Lo, Hi) -> N</name> + <name since="">rand_uniform(Lo, Hi) -> N</name> <fsummary>Generate a random number</fsummary> <type> <v>Lo, Hi, N = integer()</v> @@ -934,7 +934,7 @@ </func> <func> - <name name="start" arity="0"/> + <name name="start" arity="0" since=""/> <fsummary> Equivalent to application:start(crypto). </fsummary> <desc> <p> Equivalent to application:start(crypto).</p> @@ -942,7 +942,7 @@ </func> <func> - <name name="stop" arity="0"/> + <name name="stop" arity="0" since=""/> <fsummary> Equivalent to application:stop(crypto).</fsummary> <desc> <p> Equivalent to application:stop(crypto).</p> @@ -950,7 +950,7 @@ </func> <func> - <name name="strong_rand_bytes" arity="1"/> + <name name="strong_rand_bytes" arity="1" since="OTP R14B03"/> <fsummary>Generate a binary of random bytes</fsummary> <desc> <p>Generates N bytes randomly uniform 0..255, and returns the @@ -963,7 +963,7 @@ </func> <func> - <name name="rand_seed" arity="0"/> + <name name="rand_seed" arity="0" since="OTP 20.0"/> <fsummary>Strong random number generation plugin state</fsummary> <desc> <p> @@ -991,7 +991,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> </func> <func> - <name name="rand_seed_s" arity="0"/> + <name name="rand_seed_s" arity="0" since="OTP 20.0"/> <fsummary>Strong random number generation plugin state</fsummary> <desc> <p> @@ -1026,7 +1026,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> </func> <func> - <name>rand_seed_alg(Alg) -> rand:state()</name> + <name since="OTP 21.0">rand_seed_alg(Alg) -> rand:state()</name> <fsummary>Strong random number generation plugin state</fsummary> <type> <v>Alg = crypto | crypto_cache</v> @@ -1057,7 +1057,7 @@ _FloatValue = rand:uniform(). % [0.0; 1.0[</pre> </func> <func> - <name>rand_seed_alg(Alg, Seed) -> rand:state()</name> + <name since="OTP-22.0">rand_seed_alg(Alg, Seed) -> rand:state()</name> <fsummary>Strong random number generation plugin state</fsummary> <type> <v>Alg = crypto_aes</v> @@ -1085,7 +1085,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name>rand_seed_alg_s(Alg) -> rand:state()</name> + <name since="OTP 21.0">rand_seed_alg_s(Alg) -> rand:state()</name> <fsummary>Strong random number generation plugin state</fsummary> <type> <v>Alg = crypto | crypto_cache</v> @@ -1149,7 +1149,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name>rand_seed_alg_s(Alg, Seed) -> rand:state()</name> + <name since="OTP 22.0">rand_seed_alg_s(Alg, Seed) -> rand:state()</name> <fsummary>Strong random number generation plugin state</fsummary> <type> <v>Alg = crypto_aes</v> @@ -1215,7 +1215,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="stream_init" arity="2"/> + <name name="stream_init" arity="2" since="OTP R16B01"/> <fsummary></fsummary> <desc> <p>Initializes the state for use in RC4 stream encryption @@ -1228,7 +1228,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="stream_init" arity="3"/> + <name name="stream_init" arity="3" since="OTP R16B01"/> <fsummary></fsummary> <desc> <p>Initializes the state for use in streaming AES encryption using Counter mode (CTR). @@ -1243,7 +1243,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="stream_encrypt" arity="2"/> + <name name="stream_encrypt" arity="2" since="OTP R16B01"/> <fsummary></fsummary> <desc> <p>Encrypts <c>PlainText</c> according to the stream cipher <c>Type</c> specified in stream_init/3. @@ -1254,7 +1254,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="stream_decrypt" arity="2"/> + <name name="stream_decrypt" arity="2" since="OTP R16B01"/> <fsummary></fsummary> <desc> <p>Decrypts <c>CipherText</c> according to the stream cipher <c>Type</c> specified in stream_init/3. @@ -1265,7 +1265,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="supports" arity="0"/> + <name name="supports" arity="0" since="OTP R16B01"/> <fsummary>Provide a list of available crypto algorithms.</fsummary> <desc> <p> Can be used to determine which crypto algorithms that are supported @@ -1277,7 +1277,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="ec_curves" arity="0"/> + <name name="ec_curves" arity="0" since="OTP 17.0"/> <fsummary>Provide a list of available named elliptic curves.</fsummary> <desc> <p>Can be used to determine which named elliptic curves are supported.</p> @@ -1285,7 +1285,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="ec_curve" arity="1"/> + <name name="ec_curve" arity="1" since="OTP 17.0"/> <fsummary>Get the defining parameters of a elliptic curve.</fsummary> <desc> <p>Return the defining parameters of a elliptic curve.</p> @@ -1293,8 +1293,8 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="sign" arity="4"/> - <name name="sign" arity="5"/> + <name name="sign" arity="4" since="OTP R16B01"/> + <name name="sign" arity="5" since="OTP 20.1"/> <fsummary> Create digital signature.</fsummary> <desc> <p>Creates a digital signature.</p> @@ -1308,8 +1308,8 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="verify" arity="5"/> - <name name="verify" arity="6"/> + <name name="verify" arity="5" since="OTP R16B01"/> + <name name="verify" arity="6" since="OTP 20.1"/> <fsummary>Verifies a digital signature.</fsummary> <desc> <p>Verifies a digital signature</p> @@ -1325,7 +1325,7 @@ FloatValue = rand:uniform(). % again <!-- Engine functions --> <func> - <name name="privkey_to_pubkey" arity="2"/> + <name name="privkey_to_pubkey" arity="2" since="OTP 20.2"/> <fsummary>Fetches a public key from an Engine stored private key.</fsummary> <desc> <p>Fetches the corresponding public key from a private key stored in an Engine. @@ -1335,7 +1335,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_get_all_methods" arity="0"/> + <name name="engine_get_all_methods" arity="0" since="OTP 20.2"/> <fsummary>Return list of all possible engine methods</fsummary> <desc> <p> @@ -1353,7 +1353,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_load" arity="3"/> + <name name="engine_load" arity="3" since="OTP 20.2"/> <fsummary>Dynamical load an encryption engine</fsummary> <desc> <p> @@ -1375,7 +1375,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_load" arity="4"/> + <name name="engine_load" arity="4" since="OTP 20.2"/> <fsummary>Dynamical load an encryption engine</fsummary> <desc> <p> @@ -1395,7 +1395,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_unload" arity="1"/> + <name name="engine_unload" arity="1" since="OTP 20.2"/> <fsummary>Dynamical load an encryption engine</fsummary> <desc> <p> @@ -1415,7 +1415,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_by_id" arity="1"/> + <name name="engine_by_id" arity="1" since="OTP 21.0.6"/> <fsummary>Get a reference to an already loaded engine</fsummary> <desc> <p> @@ -1435,7 +1435,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_ctrl_cmd_string" arity="3"/> + <name name="engine_ctrl_cmd_string" arity="3" since="OTP 20.2"/> <fsummary>Sends ctrl commands to an OpenSSL engine</fsummary> <desc> <p> @@ -1452,7 +1452,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_ctrl_cmd_string" arity="4"/> + <name name="engine_ctrl_cmd_string" arity="4" since="OTP 20.2"/> <fsummary>Sends ctrl commands to an OpenSSL engine</fsummary> <desc> <p> @@ -1473,7 +1473,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_add" arity="1"/> + <name name="engine_add" arity="1" since="OTP 21.0.6"/> <fsummary>Add engine to OpenSSL internal list</fsummary> <desc> <p>Add the engine to OpenSSL's internal list.</p> @@ -1486,7 +1486,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_remove" arity="1"/> + <name name="engine_remove" arity="1" since="OTP 21.0.6"/> <fsummary>Remove engine to OpenSSL internal list</fsummary> <desc> <p>Remove the engine from OpenSSL's internal list.</p> @@ -1499,7 +1499,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_get_id" arity="1"/> + <name name="engine_get_id" arity="1" since="OTP 21.0.6"/> <fsummary>Fetch engine ID</fsummary> <desc> <p>Return the ID for the engine, or an empty binary if there is no id set.</p> @@ -1512,7 +1512,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_get_name" arity="1"/> + <name name="engine_get_name" arity="1" since="OTP 21.0.6"/> <fsummary>Fetch engine name</fsummary> <desc> <p>Return the name (eg a description) for the engine, or an empty binary if there is no name set.</p> @@ -1525,7 +1525,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="engine_list" arity="0"/> + <name name="engine_list" arity="0" since="OTP 20.2"/> <fsummary>List the known engine ids</fsummary> <desc> <p>List the id's of all engines in OpenSSL's internal list.</p> @@ -1545,7 +1545,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="ensure_engine_loaded" arity="2"/> + <name name="ensure_engine_loaded" arity="2" since="OTP 21.0.6"/> <fsummary>Ensure encryption engine just loaded once</fsummary> <desc> <p> @@ -1567,7 +1567,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="ensure_engine_loaded" arity="3"/> + <name name="ensure_engine_loaded" arity="3" since="OTP 21.0.6"/> <fsummary>Ensure encryption engine just loaded once</fsummary> <desc> <p> @@ -1590,7 +1590,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="ensure_engine_unloaded" arity="1"/> + <name name="ensure_engine_unloaded" arity="1" since="OTP 21.0.6"/> <fsummary>Unload an engine loaded with the ensure function</fsummary> <desc> <p> @@ -1613,7 +1613,7 @@ FloatValue = rand:uniform(). % again </func> <func> - <name name="ensure_engine_unloaded" arity="2"/> + <name name="ensure_engine_unloaded" arity="2" since="OTP 21.0.6"/> <fsummary>Unload an engine loaded with the ensure function</fsummary> <desc> <p> diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml index d81a8ddd87..0a3f68ade2 100644 --- a/lib/crypto/doc/src/notes.xml +++ b/lib/crypto/doc/src/notes.xml @@ -31,6 +31,43 @@ </header> <p>This document describes the changes made to the Crypto application.</p> +<section><title>Crypto 4.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Updated the RSA options part in the crypto application's + C-code, documentation and tests.</p> + <p> + Own Id: OTP-15302</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Added ed25519 and ed448 sign/verify.</p> + <p> + Requires OpenSSL 1.1.1 or higher as cryptolib under the + OTP application <c>crypto</c>.</p> + <p> + Own Id: OTP-15419 Aux Id: OTP-15094 </p> + </item> + <item> + <p> + Fixed valgrind warnings.</p> + <p> + Own Id: OTP-15467</p> + </item> + </list> + </section> + +</section> + <section><title>Crypto 4.3.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index feb02d0f5a..6836e30a1b 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -1351,7 +1351,11 @@ engine_load_1(Engine, PreCmds, PostCmds, EngineMethods) -> throw:Error -> %% The engine couldn't initialise, release the structural reference ok = engine_free_nif(Engine), - throw(Error) + throw(Error); + error:badarg -> + %% For example bad argument list, release the structural reference + ok = engine_free_nif(Engine), + error(badarg) end. engine_load_2(Engine, PostCmds, EngineMethods) -> diff --git a/lib/crypto/test/Makefile b/lib/crypto/test/Makefile index e046a25338..8b320e01a9 100644 --- a/lib/crypto/test/Makefile +++ b/lib/crypto/test/Makefile @@ -6,6 +6,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk # ---------------------------------------------------- MODULES = \ + crypto_bench_SUITE \ blowfish_SUITE \ crypto_SUITE \ engine_SUITE @@ -77,7 +78,7 @@ release_spec: release_tests_spec: $(TEST_TARGET) $(INSTALL_DIR) "$(RELSYSDIR)" - $(INSTALL_DATA) crypto.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)" + $(INSTALL_DATA) crypto.spec crypto_bench.spec crypto.cover $(RELTEST_FILES) "$(RELSYSDIR)" @tar cfh - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) chmod -R u+w "$(RELSYSDIR)" diff --git a/lib/crypto/test/crypto.spec b/lib/crypto/test/crypto.spec index cc09970cb3..4a95275687 100644 --- a/lib/crypto/test/crypto.spec +++ b/lib/crypto/test/crypto.spec @@ -1 +1,6 @@ {suites,"../crypto_test",all}. + +{skip_suites, "../crypto_test", [crypto_bench_SUITE + ], + "Benchmarks run separately"}. + diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 8e20304dc9..003e0c58b1 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -38,6 +38,7 @@ all() -> mod_pow, exor, rand_uniform, + rand_threads, rand_plugin, rand_plugin_s ]. @@ -155,7 +156,7 @@ groups() -> ]}, {dh, [], [generate_compute, compute_bug]}, - {ecdh, [], [generate_all_supported, compute, generate]}, + {ecdh, [], [use_all_elliptic_curves, compute, generate]}, {srp, [], [generate_compute]}, {des_cbc, [], [block]}, {des_cfb, [], [block]}, @@ -562,32 +563,43 @@ compute(Config) when is_list(Config) -> Gen = proplists:get_value(compute, Config), lists:foreach(fun do_compute/1, Gen). %%-------------------------------------------------------------------- -generate_all_supported() -> - [{doc, " Test that all curves from crypto:ec_curves/0 returns two binaries"}]. -generate_all_supported(_Config) -> +use_all_elliptic_curves() -> + [{doc, " Test that all curves from crypto:ec_curves/0"}]. +use_all_elliptic_curves(_Config) -> + Msg = <<"hello world!">>, + Sups = crypto:supports(), + Curves = proplists:get_value(curves, Sups), + Hashs = proplists:get_value(hashs, Sups), + ct:log("Lib: ~p~nFIPS: ~p~nCurves:~n~p~nHashs: ~p", [crypto:info_lib(), + crypto:info_fips(), + Curves, + Hashs]), Results = - [try - crypto:generate_key(ecdh, C) - of - {B1,B2} when is_binary(B1) and is_binary(B2) -> - %% That is, seems like it works as expected. - {ok,C}; - Err -> - ct:log("ERROR: Curve ~p generated ~p", [C,Err]), - {error,{C,Err}} - catch - Cls:Err:Stack -> - ct:log("ERROR: Curve ~p exception ~p:~p~n~p", [C,Cls,Err,Stack]), - {error,{C,{Cls,Err}}} - end - || C <- crypto:ec_curves(), - not lists:member(C, [ed25519, ed448]) + [{{Curve,Hash}, + try + {Pub,Priv} = crypto:generate_key(ecdh, Curve), + true = is_binary(Pub), + true = is_binary(Priv), + Sig = crypto:sign(ecdsa, Hash, Msg, [Priv, Curve]), + crypto:verify(ecdsa, Hash, Msg, Sig, [Pub, Curve]) + catch + C:E -> + {C,E} + end} + || Curve <- Curves -- [ed25519, ed448, x25519, x448, ipsec3, ipsec4], + Hash <- Hashs -- [md4, md5, ripemd160, sha3_224, sha3_256, sha3_384, sha3_512] ], - OK = [C || {ok,C} <- Results], - ct:log("Ok (len=~p): ~p", [length(OK), OK]), - false = lists:any(fun({error,_}) -> true; - (_) -> false - end, Results). + Fails = + lists:filter(fun({_,true}) -> false; + (_) -> true + end, Results), + case Fails of + [] -> + ok; + _ -> + ct:log("Fails:~n~p",[Fails]), + ct:fail("Bad curve(s)",[]) + end. %%-------------------------------------------------------------------- generate() -> @@ -614,6 +626,25 @@ rand_uniform(Config) when is_list(Config) -> 10 = byte_size(crypto:strong_rand_bytes(10)). %%-------------------------------------------------------------------- +rand_threads() -> + [{doc, "strong_rand_bytes in parallel threads"}]. +rand_threads(Config) when is_list(Config) -> + %% This will crash the emulator on at least one version of libcrypto + %% with buggy multithreading in RAND_bytes(). + %% The test needs to run at least a few minutes... + NofThreads = 4, + Fun = fun F() -> crypto:strong_rand_bytes(16), F() end, + PidRefs = [spawn_monitor(Fun) || _ <- lists:seq(1, NofThreads)], +%%% The test case takes too much time to run. +%%% Keep it around for reference by setting it down to just 10 seconds. +%%% receive after 10 * 60 * 1000 -> ok end, % 10 minutes + receive after 10 * 1000 -> ok end, % 10 seconds + spawn_link(fun () -> receive after 5000 -> exit(timeout) end end), + [exit(Pid, stop) || {Pid,_Ref} <- PidRefs], + [receive {'DOWN',Ref,_,_,stop} -> ok end || {_Pid,Ref} <- PidRefs], + ok. + +%%-------------------------------------------------------------------- rand_plugin() -> [{doc, "crypto rand plugin testing (implicit state / process dictionary)"}]. rand_plugin(Config) when is_list(Config) -> diff --git a/lib/crypto/test/crypto_bench.spec b/lib/crypto/test/crypto_bench.spec new file mode 100644 index 0000000000..b9a26d94db --- /dev/null +++ b/lib/crypto/test/crypto_bench.spec @@ -0,0 +1,3 @@ +{suites, "../crypto_test", [ + crypto_bench_SUITE + ]}. diff --git a/lib/crypto/test/crypto_bench_SUITE.erl b/lib/crypto/test/crypto_bench_SUITE.erl new file mode 100644 index 0000000000..e1fd0a63e5 --- /dev/null +++ b/lib/crypto/test/crypto_bench_SUITE.erl @@ -0,0 +1,387 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2009-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(crypto_bench_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct_event.hrl"). +-include_lib("common_test/include/ct.hrl"). + +suite() -> [%%{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}, + {timetrap,{minutes,2}} + ]. + +all() -> + [ + {group, ciphers_128} + ]. + +groups() -> + [ + {ciphers_128, [{repeat, 3}], [{group,textblock_256} + ]}, + + {textblock_256, [{repeat,2}], [ + block, + stream + ]} + ]. + +%%%---------------------------------------------------------------- +%%% +init_per_suite(Config) -> + try crypto:start() of + _ -> + [{_,_,Info}] = crypto:info_lib(), + ct:comment("~s",[Info]), + ct:pal("Crypto version: ~p~n~n~p",[Info,crypto:supports()]), + [{sec_goal,5} | Config] + catch _:_ -> + {fail, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + application:stop(crypto). + +%%%---------------------------------------------------------------- +%%% +init_per_group(Group, Config0) -> + ct:pal("~p(~p,..)",[?FUNCTION_NAME,Group]), + + Config = calibrate(Config0), + case atom_to_list(Group) of + "ciphers_"++KeySizeStr -> + KeySize = list_to_integer(KeySizeStr), + [{key_size,KeySize} + | measure_openssl_aes_cbc(KeySize, Config)]; + + "textblock_"++BlockSizeStr -> + BlockSize = list_to_integer(BlockSizeStr), + [{block_size,BlockSize} | Config]; + + _ -> + Config + end. + +end_per_group(_Group, Config) -> + Config. + + +measure_openssl_aes_cbc(KeySize, Config) -> + BLno_acc = [baseline(aes_cbc, KeySize, false)], + ct:pal("Non-accelerated baseline encryption time [µs/block]:~n~p", [BLno_acc]), + BLacc = [baseline(aes_cbc, KeySize, true)], + ct:pal("Possibly accelerated baseline encryption time [µs/block]:~n~p", [BLacc]), + [{acc,BLacc}, + {no_acc,BLno_acc} | Config]. + +calibrate(Config) -> + Secs = proplists:get_value(sec_goal, Config, 5), + {_,Empty} = data(empty, 0, 0), + {Ne,Te} = run1(Secs*2000, Empty), + [{overhead,Te/Ne} | Config]. + +%%%================================================================ +%%% +%%% +block(Config) -> + run_cryptos([aes_cbc, aes_gcm, aes_ccm, chacha20_poly1305], + Config). + +stream(Config) -> + run_cryptos([aes_ctr, chacha20], + Config). + +%%%================================================================ +%%% +%%% + +run_cryptos(Cryptos, Config) -> + run_cryptos(Cryptos, 1, Config). + +run_cryptos(Cryptos, Factor, Config) -> + KeySize = proplists:get_value(key_size, Config), + BlockSize = proplists:get_value(block_size, Config), + MilliSecGoal = 1000*proplists:get_value(sec_goal,Config), + OverHead = proplists:get_value(overhead, Config, 0), + [try + Factor*run(Crypto,KeySize,BlockSize,MilliSecGoal) - OverHead + of + TimePerOp -> % µs + %% First, Report speed of encrypting blocks of 1000. [blocks/sec] + ReportUnit = 1000, + Label = [fmt(Crypto)," key:",KeySize," block:",BlockSize], + report(Label, + (BlockSize/ReportUnit)*1000000/TimePerOp + ), + + EffCrypto = case Crypto of + X -> X + end, + %% Percent of accelerated speed + case find_value([acc,{EffCrypto,KeySize},BlockSize], Config) of + undefined -> + ok; + TimePerOpBaseAcc -> + report(["Percent of acc OpenSSL "|Label], + 100*TimePerOpBaseAcc/TimePerOp % Percent of base *speed* + ) + end, + + %% Percent of non-accelerated speed + case find_value([no_acc,{EffCrypto,KeySize},BlockSize], Config) of + undefined -> + ok; + TimePerOpBaseNoAcc -> + report(["Percent of noacc OpenSSL "|Label], + 100*TimePerOpBaseNoAcc/TimePerOp % Percent of base *speed* + ) + end + catch + _:_ -> + ct:pal("~p unsupported",[{Crypto,KeySize,BlockSize}]) + end + || Crypto <- Cryptos, + supported(Crypto) + ]. + + +run(Crypto, KeySize, BlockSize, MilliSecGoal) -> + {_Type, Funs} = data(Crypto, KeySize, BlockSize), + {Nc,Tc} = run1(MilliSecGoal, Funs), + Tc/Nc. + +fmt(X) -> X. + + +find_value(KeyPath, PropList, Default) -> + try find_value(KeyPath, PropList) + of + undefined -> Default + catch + error:function_clause -> Default + end. + +find_value(KeyPath, PropList) -> + lists:foldl(fun(K, L) when is_list(L) -> proplists:get_value(K,L); + (_, _) -> undefined + end, PropList, KeyPath). + +%%%================================================================ +%%% +%%% +funs({block, {Type, Key, IV, Block}}) -> + {fun() -> ok end, + fun(_) -> crypto:block_encrypt(Type, Key, IV, Block) end, + fun(_) -> ok end}; + +funs({stream, {Type, Key, IV, Block}}) -> + {fun() -> {crypto:stream_init(Type, Key, IV),ok} end, + fun({Ctx,_}) -> crypto:stream_encrypt(Ctx, Block) end, + fun(_) -> ok end}. + + +data(aes_cbc, KeySize, BlockSize) -> + Type = case KeySize of + 128 -> aes_cbc128; + 256 -> aes_cbc256 + end, + Key = mk_bin(KeySize div 8), + IV = mk_bin(16), + Block = mk_bin(BlockSize), + {Type, funs({block, {Type, Key, IV, Block}})}; + +data(aes_gcm, KeySize, BlockSize) -> + Type = aes_gcm, + Key = mk_bin(KeySize div 8), + IV = mk_bin(12), + Block = mk_bin(BlockSize), + AAD = <<01,02,03,04>>, + {Type, funs({block, {Type, Key, IV, {AAD,Block,16}}})}; + +data(aes_ccm, KeySize, BlockSize) -> + Type = aes_ccm, + Key = mk_bin(KeySize div 8), + IV = mk_bin(12), + Block = mk_bin(BlockSize), + AAD = <<01,02,03,04>>, + {Type, funs({block, {Type, Key, IV, {AAD,Block,12}}})}; + +data(aes_ctr, KeySize, BlockSize) -> + Type = aes_ctr, + Key = mk_bin(KeySize div 8), + IV = mk_bin(16), + Block = mk_bin(BlockSize), + {Type, funs({stream, {Type, Key, IV, Block}})}; + +data(chacha20_poly1305, 256=KeySize, BlockSize) -> + Type = chacha20_poly1305, + Key = mk_bin(KeySize div 8), + IV = mk_bin(16), + AAD = <<01,02,03,04>>, + Block = mk_bin(BlockSize), + {Type, funs({block, {Type, Key, IV, {AAD,Block}}})}; + +data(chacha20, 256=KeySize, BlockSize) -> + Type = chacha20, + Key = mk_bin(KeySize div 8), + IV = mk_bin(16), + Block = mk_bin(BlockSize), + {Type, funs({stream, {Type, Key, IV, Block}})}; + +data(empty, 0, 0) -> + {undefined, + {fun() -> ok end, + fun(X) -> X end, + fun(_) -> ok end}}. + +%%%================================================================ +%%% +%%% +run1(MilliSecGoal, Funs) -> + Parent = self(), + Pid = spawn(fun() -> + {Fi,Fu,Ff} = Funs, + Ctx0 = Fi(), + T0 = start_time(), + {N,Ctx} = loop(Fu, Ctx0, 0), + T = elapsed_time(T0), + Ff(Ctx), + Parent ! {result,N,microseconds(T)} + end), + Pid ! go, + receive + after MilliSecGoal -> + Pid ! stop + end, + receive + {result,N,MicroSecs} -> + {N,MicroSecs} + end. + + +loop(F, Ctx, N) -> + receive + stop -> + {N, Ctx} + after 0 -> + loop(F, F(Ctx), N+1) + end. + +%%%---------------------------------------------------------------- +report(LabelList, Value) -> + Label = report_chars(lists:concat(LabelList)), + ct:pal("ct_event:notify ~p: ~p", [Label, Value]), + ct_event:notify( + #event{name = benchmark_data, + data = [{name, Label}, + {value,Value}]}). + +report_chars(Cs) -> + [case C of + $- -> $_; + _ -> C + end || C <- Cs]. + +%%%---------------------------------------------------------------- +supported(Algorithm) -> + lists:member(Algorithm, + [A || {_,As} <- crypto:supports(), A <- As] + ). + +%%%---------------------------------------------------------------- +start_time() -> + erlang:system_time(). + +elapsed_time(StartTime) -> + erlang:system_time() - StartTime. + +microseconds(Time) -> + erlang:convert_time_unit(Time, native, microsecond). + +%%%---------------------------------------------------------------- + +%% Example output: +%% +DT:aes-128-cbc:3:16 +%% +R:135704772:aes-128-cbc:2.980000 +%% +DT:aes-128-cbc:3:64 +%% +R:36835089:aes-128-cbc:3.000000 +%% +DT:aes-128-cbc:3:256 +%% +R:9398616:aes-128-cbc:3.000000 +%% +DT:aes-128-cbc:3:1024 +%% +R:2355683:aes-128-cbc:2.990000 +%% +DT:aes-128-cbc:3:8192 +%% +R:294508:aes-128-cbc:2.990000 +%% +H:16:64:256:1024:8192 +%% +F:22:aes-128-cbc:728616225.50:785815232.00:802015232.00:806762338.46:806892821.40 + +baseline(Crypto, KeySize, EVP) -> + Spec= + case {Crypto,KeySize} of + {aes_cbc, 128} -> "aes-128-cbc"; + {aes_cbc, 256} -> "aes-256-cbc" + end, + {{Crypto,KeySize}, baseline(Spec, EVP)}. + +baseline(Spec, EVP) -> + Cmd = + case EVP of + true -> "openssl speed -mr -evp " ++ Spec; + false-> "openssl speed -mr " ++ Spec + end, + get_base_values(string:tokens(os:cmd(Cmd),"\n"), Spec, []). + + +get_base_values(["+DT:"++Sdt, + "+R:"++Sr + |T], Crypto, Acc) -> + [Crypto0,_GoalSecs0,BlockSize0] = string:tokens(Sdt, ":"), + [Nblocks0,Crypto0,RealSecs0] = string:tokens(Sr, ":"), + Crypto = fix_possible_space_bug(Crypto0), + RealSecs = list_to_float(RealSecs0), + BlockSize = list_to_integer(BlockSize0), + Nblocks = list_to_integer(Nblocks0), + get_base_values(T, Crypto, [{BlockSize, 1000000*RealSecs/Nblocks} | Acc]); + +get_base_values([_|T], Crypto, Acc) -> + get_base_values(T, Crypto, Acc); + +get_base_values([], _, Acc) -> + lists:sort(Acc). + +fix_possible_space_bug(S) -> lists:concat(lists:join("-",string:tokens(S,"- "))). + +%%%---------------------------------------------------------------- +mk_bin(Size) when Size =< 256 -> + list_to_binary(lists:seq(0,Size-1)); + +mk_bin(Size) when 1024 =< Size -> + B = mk_bin(Size div 4), + Brest = mk_bin(Size rem 4), + <<B/binary, B/binary, B/binary, B/binary, Brest/binary>>; + +mk_bin(Size) when 256 < Size -> + B = mk_bin(Size div 2), + Brest = mk_bin(Size rem 2), + <<B/binary, B/binary, Brest/binary>>. + diff --git a/lib/crypto/test/engine_SUITE.erl b/lib/crypto/test/engine_SUITE.erl index 3f26493cd3..2a89fa71a3 100644 --- a/lib/crypto/test/engine_SUITE.erl +++ b/lib/crypto/test/engine_SUITE.erl @@ -51,12 +51,14 @@ all() -> ctrl_cmd_string, ctrl_cmd_string_optional, ensure_load, - {group, engine_stored_key} + {group, engine_stored_key}, + {group, engine_fakes_rsa} ]. groups() -> [{engine_stored_key, [], - [sign_verify_rsa, + [ + sign_verify_rsa, sign_verify_dsa, sign_verify_ecdsa, sign_verify_rsa_pwd, @@ -71,7 +73,10 @@ groups() -> get_pub_from_priv_key_rsa_pwd_bad_pwd, get_pub_from_priv_key_dsa, get_pub_from_priv_key_ecdsa - ]}]. + ]}, + {engine_fakes_rsa, [], [sign_verify_rsa_fake + ]} + ]. init_per_suite(Config) -> @@ -102,7 +107,20 @@ end_per_suite(_Config) -> %%-------------------------------------------------------------------- init_per_group(engine_stored_key, Config) -> - case load_storage_engine(Config) of + group_load_engine(Config, [engine_method_rsa]); +init_per_group(engine_fakes_rsa, Config) -> + case crypto:info_lib() of + [{<<"OpenSSL">>,LibVer,_}] when is_integer(LibVer), LibVer >= 16#10100000 -> + group_load_engine(Config, []); + _ -> + {skip, "Too low OpenSSL cryptolib version"} + end; +init_per_group(_Group, Config0) -> + Config0. + + +group_load_engine(Config, ExcludeMthds) -> + case load_storage_engine(Config, ExcludeMthds) of {ok, E} -> KeyDir = key_dir(Config), [{storage_engine,E}, {storage_dir,KeyDir} | Config]; @@ -115,19 +133,19 @@ init_per_group(engine_stored_key, Config) -> Other -> ct:log("Engine load failed: ~p",[Other]), {fail, "Engine load failed"} - end; -init_per_group(_Group, Config0) -> - Config0. + end. + + -end_per_group(engine_stored_key, Config) -> + + +end_per_group(_, Config) -> case proplists:get_value(storage_engine, Config) of undefined -> ok; E -> ok = crypto:engine_unload(E) - end; -end_per_group(_, _) -> - ok. + end. %%-------------------------------------------------------------------- init_per_testcase(_Case, Config) -> @@ -421,6 +439,9 @@ bad_arguments(Config) when is_list(Config) -> try try crypto:engine_load(fail_engine, [], []) + of + X1 -> + ct:fail("1 Got ~p",[X1]) catch error:badarg -> ok @@ -432,6 +453,11 @@ bad_arguments(Config) when is_list(Config) -> {<<"ID">>, <<"MD5">>}, <<"LOAD">>], []) + of + {error,bad_engine_id} -> + throw(dynamic_engine_unsupported); + X2 -> + ct:fail("2 Got ~p",[X2]) catch error:badarg -> ok @@ -442,13 +468,20 @@ bad_arguments(Config) when is_list(Config) -> {'ID', <<"MD5">>}, <<"LOAD">>], []) + of + {error,bad_engine_id} -> % should have happend in the previous try...catch end! + throw(dynamic_engine_unsupported); + X3 -> + ct:fail("3 Got ~p",[X3]) catch error:badarg -> ok end catch error:notsup -> - {skip, "Engine not supported on this SSL version"} + {skip, "Engine not supported on this SSL version"}; + throw:dynamic_engine_unsupported -> + {skip, "Dynamic Engine not supported"} end end. @@ -554,11 +587,11 @@ ctrl_cmd_string(Config) when is_list(Config) -> {ok, E} -> case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>) of ok -> + ok = crypto:engine_unload(E), ct:fail(fail_ctrl_cmd_should_fail); {error,ctrl_cmd_failed} -> - ok - end, - ok = crypto:engine_unload(E); + ok = crypto:engine_unload(E) + end; {error, bad_engine_id} -> {skip, "Dynamic Engine not supported"} end @@ -584,11 +617,12 @@ ctrl_cmd_string_optional(Config) when is_list(Config) -> {ok, E} -> case crypto:engine_ctrl_cmd_string(E, <<"TEST">>, <<"17">>, true) of ok -> - ok; - _ -> + ok = crypto:engine_unload(E); + Err -> + ct:log("Error: ~p",[Err]), + ok = crypto:engine_unload(E), ct:fail(fail_ctrl_cmd_string) - end, - ok = crypto:engine_unload(E); + end; {error, bad_engine_id} -> {skip, "Dynamic Engine not supported"} end @@ -650,6 +684,14 @@ sign_verify_rsa(Config) -> key_id => key_id(Config, "rsa_public_key.pem")}, sign_verify(rsa, sha, Priv, Pub). +sign_verify_rsa_fake(Config) -> + %% Use fake engine rsa implementation + Priv = #{engine => engine_ref(Config), + key_id => key_id(Config, "rsa_private_key.pem")}, + Pub = #{engine => engine_ref(Config), + key_id => key_id(Config, "rsa_public_key.pem")}, + sign_verify_fake(rsa, sha256, Priv, Pub). + sign_verify_dsa(Config) -> Priv = #{engine => engine_ref(Config), key_id => key_id(Config, "dsa_private_key.pem")}, @@ -809,13 +851,18 @@ get_pub_from_priv_key_ecdsa(Config) -> %%%================================================================ %%% Help for engine_stored_pub_priv_keys* test cases %%% -load_storage_engine(_Config) -> +load_storage_engine(Config) -> + load_storage_engine(Config, []). + +load_storage_engine(_Config, ExcludeMthds) -> case crypto:get_test_engine() of {ok, Engine} -> try crypto:engine_load(<<"dynamic">>, [{<<"SO_PATH">>, Engine}, <<"LOAD">>], - []) + [], + crypto:engine_get_all_methods() -- ExcludeMthds + ) catch error:notsup -> {error, notsup} @@ -873,10 +920,47 @@ sign_verify(Alg, Sha, KeySign, KeyVerify) -> true -> PlainText = <<"Hej på dig">>, Signature = crypto:sign(Alg, Sha, PlainText, KeySign), - case crypto:verify(Alg, Sha, PlainText, Signature, KeyVerify) of - true -> ok; - _ -> {fail, "Sign-verify error"} + case is_fake(Signature) of + true -> + ct:pal("SIG ~p ~p size ~p~n~p",[Alg,Sha,size(Signature),Signature]), + {fail, "Faked RSA impl used!!"}; + false -> + case crypto:verify(Alg, Sha, PlainText, Signature, KeyVerify) of + true -> ok; + _ -> {fail, "Sign-verify error"} + end + end; + false -> + {skip, lists:concat([Alg," is not supported by cryptolib"])} + end. + + +%%% Use fake engine rsa implementation +sign_verify_fake(Alg, Sha, KeySign, KeyVerify) -> + case pubkey_alg_supported(Alg) of + true -> + PlainText = <<"Fake me!">>, + Signature = crypto:sign(Alg, Sha, PlainText, KeySign), + case is_fake(Signature) of + true -> + case crypto:verify(Alg, Sha, PlainText, Signature, KeyVerify) of + true -> ok; + _ -> {fail, "Sign-verify error"} + end; + false -> + ct:pal("SIG ~p ~p size ~p~n~p",[Alg,Sha,size(Signature),Signature]), + {fail, "Faked impl not used"} end; false -> {skip, lists:concat([Alg," is not supported by cryptolib"])} end. + + +is_fake(Sig) -> is_fake(Sig, 0). + +is_fake(<<>>, _) -> true; +is_fake(<<B,Rest/binary>>, B) -> is_fake(Rest, B+1); +is_fake(_, _) -> false. + + + diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk index 64d593f64a..6a91244715 100644 --- a/lib/crypto/vsn.mk +++ b/lib/crypto/vsn.mk @@ -1 +1 @@ -CRYPTO_VSN = 4.3.3 +CRYPTO_VSN = 4.4 diff --git a/lib/debugger/doc/src/debugger.xml b/lib/debugger/doc/src/debugger.xml index 1ecdbcd064..77285095e7 100644 --- a/lib/debugger/doc/src/debugger.xml +++ b/lib/debugger/doc/src/debugger.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>debugger</module> + <module since="">debugger</module> <modulesummary>Erlang Debugger.</modulesummary> <description> <p>Erlang Debugger for debugging and testing of Erlang programs.</p> @@ -36,10 +36,10 @@ <funcs> <func> - <name>start()</name> - <name>start(File)</name> - <name>start(Mode)</name> - <name>start(Mode, File)</name> + <name since="">start()</name> + <name since="">start(File)</name> + <name since="">start(Mode)</name> + <name since="">start(Mode, File)</name> <fsummary>Start Debugger.</fsummary> <type> <v>Mode = local | global</v> @@ -60,7 +60,7 @@ </func> <func> - <name>quick(Module, Name, Args)</name> + <name since="">quick(Module, Name, Args)</name> <fsummary>Debug a process.</fsummary> <type> <v>Module = Name = atom()</v> diff --git a/lib/debugger/doc/src/i.xml b/lib/debugger/doc/src/i.xml index 628b91e9e4..06b0eb876a 100644 --- a/lib/debugger/doc/src/i.xml +++ b/lib/debugger/doc/src/i.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>i</module> + <module since="">i</module> <modulesummary>Debugger/Interpreter Interface.</modulesummary> <description> <p>The <c>i</c> module provides short forms for some of @@ -51,7 +51,7 @@ <funcs> <func> - <name>im() -> pid()</name> + <name since="">im() -> pid()</name> <fsummary>Start a graphical monitor.</fsummary> <desc> <p>Starts a new graphical monitor. This is the Monitor window, @@ -63,10 +63,10 @@ </func> <func> - <name>ii(AbsModules) -> ok</name> - <name>ii(AbsModule) -> {module, Module} | error</name> - <name>ini(AbsModules) -> ok</name> - <name>ini(AbsModule) -> {module, Module} | error</name> + <name since="">ii(AbsModules) -> ok</name> + <name since="">ii(AbsModule) -> {module, Module} | error</name> + <name since="">ini(AbsModules) -> ok</name> + <name since="">ini(AbsModule) -> {module, Module} | error</name> <fsummary>Interpret a module.</fsummary> <type> <v>AbsModules = [AbsModule]</v> @@ -84,8 +84,8 @@ </func> <func> - <name>iq(AbsModule) -> ok</name> - <name>inq(AbsModule) -> ok</name> + <name since="">iq(AbsModule) -> ok</name> + <name since="">inq(AbsModule) -> ok</name> <fsummary>Stop interpreting a module.</fsummary> <type> <v>AbsModule = Module | File</v> @@ -100,7 +100,7 @@ </func> <func> - <name>il() -> ok</name> + <name since="">il() -> ok</name> <fsummary>Make a printout of all interpreted modules</fsummary> <desc> <p>Makes a printout of all interpreted modules. @@ -110,7 +110,7 @@ </func> <func> - <name>ip() -> ok</name> + <name since="">ip() -> ok</name> <fsummary>Print the current status of all interpreted processes.</fsummary> <desc> @@ -119,7 +119,7 @@ </func> <func> - <name>ic() -> ok</name> + <name since="">ic() -> ok</name> <fsummary>Clear information about processes executing interpreted code.</fsummary> <desc> @@ -129,8 +129,8 @@ </func> <func> - <name>iaa(Flags) -> true</name> - <name>iaa(Flags, Function) -> true</name> + <name since="">iaa(Flags) -> true</name> + <name since="">iaa(Flags, Function) -> true</name> <fsummary>Set when and how to attach to a process.</fsummary> <type> <v>Flags = [init | break | exit]</v> @@ -148,7 +148,7 @@ </func> <func> - <name>ist(Flag) -> true</name> + <name since="">ist(Flag) -> true</name> <fsummary>Set how to save call frames.</fsummary> <type> <v>Flag = all | no_tail | false</v> @@ -160,7 +160,7 @@ </func> <func> - <name>ia(Pid) -> ok | no_proc</name> + <name since="">ia(Pid) -> ok | no_proc</name> <fsummary>Attache to a process.</fsummary> <type> <v>Pid = pid()</v> @@ -172,7 +172,7 @@ </func> <func> - <name>ia(X,Y,Z) -> ok | no_proc</name> + <name since="">ia(X,Y,Z) -> ok | no_proc</name> <fsummary>Attache to a process.</fsummary> <type> <v>X = Y = Z = int()</v> @@ -184,7 +184,7 @@ </func> <func> - <name>ia(Pid, Function) -> ok | no_proc</name> + <name since="">ia(Pid, Function) -> ok | no_proc</name> <fsummary>Attache to a process.</fsummary> <type> <v>Pid = pid()</v> @@ -199,7 +199,7 @@ </func> <func> - <name>ia(X,Y,Z, Function) -> ok | no_proc</name> + <name since="">ia(X,Y,Z, Function) -> ok | no_proc</name> <fsummary>Attache to a process.</fsummary> <type> <v>X = Y = Z = int()</v> @@ -217,7 +217,7 @@ </func> <func> - <name>ib(Module, Line) -> ok | {error, break_exists}</name> + <name since="">ib(Module, Line) -> ok | {error, break_exists}</name> <fsummary>Create a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -229,7 +229,7 @@ </func> <func> - <name>ib(Module, Name, Arity) -> ok | {error, function_not_found} + <name since="">ib(Module, Name, Arity) -> ok | {error, function_not_found} </name> <fsummary>Create breakpoints in the specified function.</fsummary> <type> @@ -243,7 +243,7 @@ </func> <func> - <name>ir() -> ok</name> + <name since="">ir() -> ok</name> <fsummary>Delete all breakpoints.</fsummary> <desc> <p>Deletes all breakpoints.</p> @@ -251,7 +251,7 @@ </func> <func> - <name>ir(Module) -> ok</name> + <name since="">ir(Module) -> ok</name> <fsummary>Delete all breakpoints in a module.</fsummary> <type> <v>Module = atom()</v> @@ -262,7 +262,7 @@ </func> <func> - <name>ir(Module, Line) -> ok</name> + <name since="">ir(Module, Line) -> ok</name> <fsummary>Delete a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -274,7 +274,7 @@ </func> <func> - <name>ir(Module, Name, Arity) -> ok | {error, function_not_found} + <name since="">ir(Module, Name, Arity) -> ok | {error, function_not_found} </name> <fsummary>Delete breakpoints from the specified function.</fsummary> <type> @@ -288,7 +288,7 @@ </func> <func> - <name>ibd(Module, Line) -> ok</name> + <name since="">ibd(Module, Line) -> ok</name> <fsummary>Make a breakpoint inactive.</fsummary> <type> <v>Module = atom()</v> @@ -300,7 +300,7 @@ </func> <func> - <name>ibe(Module, Line) -> ok</name> + <name since="">ibe(Module, Line) -> ok</name> <fsummary>Make a breakpoint active.</fsummary> <type> <v>Module = atom()</v> @@ -312,7 +312,7 @@ </func> <func> - <name>iba(Module, Line, Action) -> ok</name> + <name since="">iba(Module, Line, Action) -> ok</name> <fsummary>Set the trigger action of a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -326,7 +326,7 @@ </func> <func> - <name>ibc(Module, Line, Function) -> ok</name> + <name since="">ibc(Module, Line, Function) -> ok</name> <fsummary>Set the conditional test of a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -348,7 +348,7 @@ </func> <func> - <name>ipb() -> ok</name> + <name since="">ipb() -> ok</name> <fsummary>Print all existing breakpoints.</fsummary> <desc> <p>Prints all existing breakpoints.</p> @@ -356,7 +356,7 @@ </func> <func> - <name>ipb(Module) -> ok</name> + <name since="">ipb(Module) -> ok</name> <fsummary>Print all existing breakpoints in a module.</fsummary> <type> <v>Module = atom()</v> @@ -367,7 +367,7 @@ </func> <func> - <name>iv() -> atom()</name> + <name since="">iv() -> atom()</name> <fsummary>Return the current version number of the interpreter. </fsummary> <desc> @@ -377,7 +377,7 @@ </func> <func> - <name>help() -> ok</name> + <name since="">help() -> ok</name> <fsummary>Print help text.</fsummary> <desc> <p>Prints help text.</p> diff --git a/lib/debugger/doc/src/int.xml b/lib/debugger/doc/src/int.xml index 31e9dfe923..a0078714e6 100644 --- a/lib/debugger/doc/src/int.xml +++ b/lib/debugger/doc/src/int.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>int</module> + <module since="">int</module> <modulesummary>Interpreter Interface.</modulesummary> <description> <p>The Erlang interpreter provides mechanisms for breakpoints and @@ -94,10 +94,10 @@ <funcs> <func> - <name>i(AbsModule) -> {module,Module} | error</name> - <name>i(AbsModules) -> ok</name> - <name>ni(AbsModule) -> {module,Module} | error</name> - <name>ni(AbsModules) -> ok</name> + <name since="">i(AbsModule) -> {module,Module} | error</name> + <name since="">i(AbsModules) -> ok</name> + <name since="">ni(AbsModule) -> {module,Module} | error</name> + <name since="">ni(AbsModules) -> ok</name> <fsummary>Interpret a module.</fsummary> <type> <v>AbsModules = [AbsModule]</v> @@ -144,8 +144,8 @@ </func> <func> - <name>n(AbsModule) -> ok</name> - <name>nn(AbsModule) -> ok</name> + <name since="">n(AbsModule) -> ok</name> + <name since="">nn(AbsModule) -> ok</name> <fsummary>Stop interpreting a module.</fsummary> <type> <v>AbsModule = Module | File | [Module | File]</v> @@ -163,7 +163,7 @@ </func> <func> - <name>interpreted() -> [Module]</name> + <name since="">interpreted() -> [Module]</name> <fsummary>Get all interpreted modules.</fsummary> <type> <v>Module = atom()</v> @@ -174,7 +174,7 @@ </func> <func> - <name>file(Module) -> File | {error,not_loaded}</name> + <name since="">file(Module) -> File | {error,not_loaded}</name> <fsummary>Get the filename for an interpreted module.</fsummary> <type> <v>Module = atom()</v> @@ -187,7 +187,7 @@ </func> <func> - <name>interpretable(AbsModule) -> true | {error,Reason}</name> + <name since="">interpretable(AbsModule) -> true | {error,Reason}</name> <fsummary>Check if a module can be interpreted.</fsummary> <type> <v>AbsModule = Module | File</v> @@ -255,9 +255,9 @@ </func> <func> - <name>auto_attach() -> false | {Flags,Function}</name> - <name>auto_attach(false)</name> - <name>auto_attach(Flags, Function)</name> + <name since="">auto_attach() -> false | {Flags,Function}</name> + <name since="">auto_attach(false)</name> + <name since="">auto_attach(Flags, Function)</name> <fsummary>Get and set when and how to attach to a process.</fsummary> <type> <v>Flags = [init | break | exit]</v> @@ -290,8 +290,8 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>stack_trace() -> Flag</name> - <name>stack_trace(Flag)</name> + <name since="">stack_trace() -> Flag</name> + <name since="">stack_trace(Flag)</name> <fsummary>Get and set if and how to save call frames.</fsummary> <type> <v>Flag = all | no_tail | false</v> @@ -322,7 +322,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>break(Module, Line) -> ok | {error,break_exists}</name> + <name since="">break(Module, Line) -> ok | {error,break_exists}</name> <fsummary>Create a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -334,7 +334,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>delete_break(Module, Line) -> ok</name> + <name since="">delete_break(Module, Line) -> ok</name> <fsummary>Delete a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -346,7 +346,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>break_in(Module, Name, Arity) -> ok + <name since="">break_in(Module, Name, Arity) -> ok | {error,function_not_found}</name> <fsummary>Create breakpoints in the specified function.</fsummary> <type> @@ -360,7 +360,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>del_break_in(Module, Name, Arity) -> ok + <name since="">del_break_in(Module, Name, Arity) -> ok | {error,function_not_found}</name> <fsummary>Delete breakpoints from the specified function.</fsummary> <type> @@ -374,8 +374,8 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>no_break() -> ok</name> - <name>no_break(Module) -> ok</name> + <name since="">no_break() -> ok</name> + <name since="">no_break(Module) -> ok</name> <fsummary>Delete all breakpoints.</fsummary> <desc> <p>Deletes all breakpoints, or all breakpoints in <c>Module</c>.</p> @@ -383,7 +383,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>disable_break(Module, Line) -> ok</name> + <name since="">disable_break(Module, Line) -> ok</name> <fsummary>Make a breakpoint inactive.</fsummary> <type> <v>Module = atom()</v> @@ -395,7 +395,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>enable_break(Module, Line) -> ok</name> + <name since="">enable_break(Module, Line) -> ok</name> <fsummary>Make a breakpoint active.</fsummary> <type> <v>Module = atom()</v> @@ -407,7 +407,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>action_at_break(Module, Line, Action) -> ok</name> + <name since="">action_at_break(Module, Line, Action) -> ok</name> <fsummary>Set the trigger action of a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -421,7 +421,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>test_at_break(Module, Line, Function) -> ok</name> + <name since="">test_at_break(Module, Line, Function) -> ok</name> <fsummary>Set the conditional test of a breakpoint.</fsummary> <type> <v>Module = atom()</v> @@ -438,7 +438,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>get_binding(Var, Bindings) -> {value,Value} | unbound</name> + <name since="">get_binding(Var, Bindings) -> {value,Value} | unbound</name> <fsummary>Retrieve a variable binding.</fsummary> <type> <v>Var = atom()</v> @@ -453,8 +453,8 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>all_breaks() -> [Break]</name> - <name>all_breaks(Module) -> [Break]</name> + <name since="">all_breaks() -> [Break]</name> + <name since="">all_breaks(Module) -> [Break]</name> <fsummary>Get all breakpoints.</fsummary> <type> <v>Break = {Point,Options}</v> @@ -474,7 +474,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>snapshot() -> [Snapshot]</name> + <name since="">snapshot() -> [Snapshot]</name> <fsummary>Get information about all processes executing interpreted code.</fsummary> <type> @@ -519,7 +519,7 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>clear() -> ok</name> + <name since="">clear() -> ok</name> <fsummary>Clear information about processes executing interpreted code.</fsummary> <desc> @@ -529,8 +529,8 @@ spawn(Module, Name, [Pid | Args])</pre> </func> <func> - <name>continue(Pid) -> ok | {error,not_interpreted}</name> - <name>continue(X,Y,Z) -> ok | {error,not_interpreted}</name> + <name since="">continue(Pid) -> ok | {error,not_interpreted}</name> + <name since="">continue(X,Y,Z) -> ok | {error,not_interpreted}</name> <fsummary>Resume process execution.</fsummary> <type> <v>Pid = pid()</v> diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml index e34ffd6def..f5e8337eb1 100644 --- a/lib/dialyzer/doc/src/dialyzer.xml +++ b/lib/dialyzer/doc/src/dialyzer.xml @@ -29,7 +29,7 @@ <rev></rev> <file>dialyzer.xml</file> </header> - <module>dialyzer</module> + <module since="">dialyzer</module> <modulesummary>Dialyzer, a DIscrepancy AnaLYZer for ERlang programs. </modulesummary> <description> @@ -472,7 +472,7 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code> <funcs> <func> - <name>format_warning(Msg) -> string()</name> + <name since="">format_warning(Msg) -> string()</name> <fsummary>Get the string version of a warning message.</fsummary> <type> <v>Msg = {Tag, Id, msg()}</v> @@ -485,8 +485,8 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code> </func> <func> - <name>gui() -> ok | {error, Msg}</name> - <name>gui(OptList) -> ok | {error, Msg}</name> + <name since="">gui() -> ok | {error, Msg}</name> + <name since="">gui(OptList) -> ok | {error, Msg}</name> <fsummary>Dialyzer GUI version.</fsummary> <type> <v>OptList</v> @@ -539,7 +539,7 @@ WarnOpts :: error_handling </func> <func> - <name>plt_info(string()) -> {'ok', [{atom(), any()}]} | {'error', atom()}</name> + <name since="">plt_info(string()) -> {'ok', [{atom(), any()}]} | {'error', atom()}</name> <fsummary>Return information about the specified PLT.</fsummary> <desc> <p>Returns information about the specified PLT.</p> @@ -547,7 +547,7 @@ WarnOpts :: error_handling </func> <func> - <name>run(OptList) -> Warnings</name> + <name since="">run(OptList) -> Warnings</name> <fsummary>Dialyzer command-line version.</fsummary> <type> <v>OptList</v> diff --git a/lib/diameter/doc/src/diameter.xml b/lib/diameter/doc/src/diameter.xml index dfa4c803ed..0a0194af2d 100644 --- a/lib/diameter/doc/src/diameter.xml +++ b/lib/diameter/doc/src/diameter.xml @@ -55,7 +55,7 @@ limitations under the License. <!-- ===================================================================== --> <!-- ===================================================================== --> -<module>diameter</module> +<module since="OTP R14B03">diameter</module> <modulesummary>Main API of the diameter application.</modulesummary> <description> @@ -1574,7 +1574,7 @@ identifies the configuration.</p> <!-- ===================================================================== --> <func> -<name>add_transport(SvcName, {connect|listen, [Opt]}) +<name since="OTP R14B03">add_transport(SvcName, {connect|listen, [Opt]}) -> {ok, Ref} | {error, Reason}</name> <fsummary>Add transport capability to a service.</fsummary> <type> @@ -1624,7 +1624,7 @@ its transports.</p> <!-- ===================================================================== --> <func> -<name>call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name> +<name since="OTP R14B03">call(SvcName, App, Request, [Opt]) -> Answer | ok | {error, Reason}</name> <fsummary>Send a Diameter request message.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -1730,7 +1730,7 @@ transport connection.</p> <!-- ===================================================================== --> <func> -<name>origin_state_id() -> &dict_Unsigned32;</name> +<name since="OTP R14B03">origin_state_id() -> &dict_Unsigned32;</name> <fsummary>Returns a reasonable Origin-State-Id.</fsummary> <desc> <p> @@ -1748,7 +1748,7 @@ at the time the diameter application was started.</p> <!-- ===================================================================== --> <func> -<name>remove_transport(SvcName, Pred) -> ok | {error, Reason}</name> +<name since="OTP R14B03">remove_transport(SvcName, Pred) -> ok | {error, Reason}</name> <fsummary>Remove previously added transports.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -1795,7 +1795,7 @@ configured on the transport.</p> <!-- ===================================================================== --> <func> -<name>service_info(SvcName, Info) -> term()</name> +<name since="OTP R14B03">service_info(SvcName, Info) -> term()</name> <fsummary>Return information about a started service.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -2114,7 +2114,7 @@ For example:</p> <!-- ===================================================================== --> <func> -<name>services() -> [SvcName]</name> +<name since="OTP R14B03">services() -> [SvcName]</name> <fsummary>Return the list of started services.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -2129,7 +2129,7 @@ Return the list of started services.</p> <!-- ===================================================================== --> <func> -<name>session_id(Ident) -> &dict_OctetString;</name> +<name since="OTP R14B03">session_id(Ident) -> &dict_OctetString;</name> <fsummary>Return a value for a Session-Id AVP.</fsummary> <type> <v>Ident = &dict_DiameterIdentity;</v> @@ -2148,7 +2148,7 @@ the message containing the returned value will be sent.</p> <!-- ===================================================================== --> <func> -<name>start() -> ok | {error, Reason}</name> +<name since="OTP R14B03">start() -> ok | {error, Reason}</name> <fsummary>Start the diameter application.</fsummary> <desc> <p> @@ -2164,7 +2164,7 @@ file, not by calling <c>start/0</c> explicitly.</p> <!-- ===================================================================== --> <func> -<name>start_service(SvcName, Options) -> ok | {error, Reason}</name> +<name since="OTP R14B03">start_service(SvcName, Options) -> ok | {error, Reason}</name> <fsummary>Start a Diameter service.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -2194,7 +2194,7 @@ necessarily the case.</p> <!-- ===================================================================== --> <func> -<name>stop() -> ok | {error, Reason}</name> +<name since="OTP R14B03">stop() -> ok | {error, Reason}</name> <fsummary>Stop the diameter application.</fsummary> <desc> <p> @@ -2208,7 +2208,7 @@ Stop the diameter application.</p> <!-- ===================================================================== --> <func> -<name>stop_service(SvcName) -> ok | {error, Reason}</name> +<name since="OTP R14B03">stop_service(SvcName) -> ok | {error, Reason}</name> <fsummary>Stop a Diameter service.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -2236,7 +2236,7 @@ be called to remove transport configuration.</p> <!-- ===================================================================== --> <func> -<name>subscribe(SvcName) -> true</name> +<name since="OTP R14B03">subscribe(SvcName) -> true</name> <fsummary>Subscribe to event messages.</fsummary> <type> <v>SvcName = &service_name;</v> @@ -2258,7 +2258,7 @@ reception of all transport-related events.</p> <!-- ===================================================================== --> <func> -<name>unsubscribe(SvcName) -> true</name> +<name since="OTP R14B03">unsubscribe(SvcName) -> true</name> <fsummary>Unsubscribe to event messages.</fsummary> <type> <v>SvcName = &service_name;</v> diff --git a/lib/diameter/doc/src/diameter_app.xml b/lib/diameter/doc/src/diameter_app.xml index aa334beb21..82e3d449ef 100644 --- a/lib/diameter/doc/src/diameter_app.xml +++ b/lib/diameter/doc/src/diameter_app.xml @@ -44,7 +44,7 @@ limitations under the License. </header> -<module>diameter_app</module> +<module since="OTP R14B03">diameter_app</module> <modulesummary> Callback module of a Diameter application.</modulesummary> @@ -180,7 +180,7 @@ process.</p> <funcs> <func> -<name>Mod:peer_up(SvcName, Peer, State) -> NewState</name> +<name since="OTP R14B03">Mod:peer_up(SvcName, Peer, State) -> NewState</name> <fsummary>Invoked when a transport connection has been established</fsummary> <type> <v>SvcName = &mod_service_name;</v> @@ -215,7 +215,7 @@ handled independently of &peer_up; and &peer_down;.</p> </func> <func> -<name>Mod:peer_down(SvcName, Peer, State) -> NewState</name> +<name since="OTP R14B03">Mod:peer_down(SvcName, Peer, State) -> NewState</name> <fsummary>Invoked when a transport connection has been lost.</fsummary> <type> <v>SvcName = &mod_service_name;</v> @@ -234,7 +234,7 @@ candidate in &pick_peer; callbacks.</p> </func> <func> -<name>Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State) +<name since="OTP R14B03">Mod:pick_peer(LocalCandidates, RemoteCandidates, SvcName, State) -> Selection | false</name> <fsummary>Select a target peer for an outgoing request.</fsummary> <type> @@ -311,7 +311,7 @@ or &peer_down; callback.</p> </func> <func> -<name>Mod:prepare_request(Packet, SvcName, Peer) -> Action</name> +<name since="OTP R14B03">Mod:prepare_request(Packet, SvcName, Peer) -> Action</name> <fsummary>Return a request for encoding and transport.</fsummary> <type> <v>Packet = &packet;</v> @@ -363,7 +363,7 @@ discarded}</c>.</p> </func> <func> -<name>Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name> +<name since="OTP R14B03">Mod:prepare_retransmit(Packet, SvcName, Peer) -> Action</name> <fsummary>Return a request for encoding and retransmission.</fsummary> <type> <v>Packet = &packet;</v> @@ -393,7 +393,7 @@ discarded}</c>.</p> </func> <func> -<name>Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name> +<name since="OTP R14B03">Mod:handle_answer(Packet, Request, SvcName, Peer) -> Result</name> <fsummary>Receive an answer message from a peer.</fsummary> <type> <v>Packet = &packet;</v> @@ -437,7 +437,7 @@ The &mod_application_opt; </func> <func> -<name>Mod:handle_error(Reason, Request, SvcName, Peer) -> Result</name> +<name since="OTP R14B03">Mod:handle_error(Reason, Request, SvcName, Peer) -> Result</name> <fsummary>Return an error from a outgoing request.</fsummary> <type> <v>Reason = timeout | failover | term()</v> @@ -465,7 +465,7 @@ not selected.</p> </func> <func> -<name>Mod:handle_request(Packet, SvcName, Peer) -> Action</name> +<name since="OTP R14B03">Mod:handle_request(Packet, SvcName, Peer) -> Action</name> <fsummary>Receive an incoming request.</fsummary> <type> <v>Packet = &packet;</v> diff --git a/lib/diameter/doc/src/diameter_codec.xml b/lib/diameter/doc/src/diameter_codec.xml index 0a34dd7ec7..0384ad2913 100644 --- a/lib/diameter/doc/src/diameter_codec.xml +++ b/lib/diameter/doc/src/diameter_codec.xml @@ -46,7 +46,7 @@ limitations under the License. <file>diameter_codec.xml</file> </header> -<module>diameter_codec</module> +<module since="OTP R15B03">diameter_codec</module> <modulesummary>Decode and encode of Diameter messages.</modulesummary> <description> @@ -346,7 +346,7 @@ question, as documented in &man_transport;.</p> <funcs> <func> -<name>decode(Mod, Bin) -> Pkt</name> +<name since="OTP R15B03">decode(Mod, Bin) -> Pkt</name> <fsummary>Decode a Diameter message.</fsummary> <type> <v>Mod = &dictionary;</v> @@ -362,7 +362,7 @@ Decode a Diameter message.</p> </func> <func> -<name>encode(Mod, Msg) -> Pkt</name> +<name since="OTP R15B03">encode(Mod, Msg) -> Pkt</name> <fsummary>Encode a Diameter message.</fsummary> <type> <v>Mod = &dictionary;</v> diff --git a/lib/diameter/doc/src/diameter_make.xml b/lib/diameter/doc/src/diameter_make.xml index 112355816f..57e83bbca1 100644 --- a/lib/diameter/doc/src/diameter_make.xml +++ b/lib/diameter/doc/src/diameter_make.xml @@ -45,7 +45,7 @@ limitations under the License. <file>diameter_make.xml</file> </header> -<module>diameter_make</module> +<module since="OTP R14B03">diameter_make</module> <modulesummary>Diameter dictionary compilation.</modulesummary> <description> @@ -67,7 +67,7 @@ interface.</p> <funcs> <func> -<name>codec(File :: iolist() | binary(), [Opt]) -> ok +<name since="OTP R15B">codec(File :: iolist() | binary(), [Opt]) -> ok | {ok, [Out]} | {error, Reason}</name> <fsummary>Compile a dictionary file into Erlang source.</fsummary> @@ -186,7 +186,7 @@ A returned error reason can be converted into a readable string using <!-- ===================================================================== --> <func> -<name>format(Parsed) -> iolist()</name> +<name since="OTP R16B03">format(Parsed) -> iolist()</name> <fsummary>Format a parsed dictionary.</fsummary> <desc> <p> @@ -198,7 +198,7 @@ dictionary format.</p> <!-- ===================================================================== --> <func> -<name>flatten(Parsed) -> term()</name> +<name since="OTP R16B03">flatten(Parsed) -> term()</name> <fsummary>Flatten a parsed dictionary.</fsummary> <desc> @@ -214,7 +214,7 @@ The return value is also a parsed dictionary.</p> <!-- ===================================================================== --> <func> -<name>format_error(Reason) -> string()</name> +<name since="OTP 17.0">format_error(Reason) -> string()</name> <fsummary>Turn an error reason into a readable string.</fsummary> <desc> diff --git a/lib/diameter/doc/src/diameter_sctp.xml b/lib/diameter/doc/src/diameter_sctp.xml index 62e958870e..2cf924ee6b 100644 --- a/lib/diameter/doc/src/diameter_sctp.xml +++ b/lib/diameter/doc/src/diameter_sctp.xml @@ -47,7 +47,7 @@ limitations under the License. <file>diameter_sctp.xml</file> </header> -<module>diameter_sctp</module> +<module since="OTP R14B03">diameter_sctp</module> <modulesummary>Diameter transport over SCTP.</modulesummary> <description> @@ -67,7 +67,7 @@ and implements the behaviour documented in <funcs> <func> -<name>start({Type, Ref}, Svc, [Opt]) +<name since="OTP R14B03">start({Type, Ref}, Svc, [Opt]) -> {ok, Pid, [LAddr]} | {error, Reason}</name> <fsummary>Start a transport process.</fsummary> <type> diff --git a/lib/diameter/doc/src/diameter_tcp.xml b/lib/diameter/doc/src/diameter_tcp.xml index 9f84eeb9fd..fe0cb2d067 100644 --- a/lib/diameter/doc/src/diameter_tcp.xml +++ b/lib/diameter/doc/src/diameter_tcp.xml @@ -57,7 +57,7 @@ limitations under the License. <file>diameter_tcp.xml</file> </header> -<module>diameter_tcp</module> +<module since="OTP R14B03">diameter_tcp</module> <modulesummary>Diameter transport over TCP.</modulesummary> <description> @@ -83,7 +83,7 @@ before configuring TLS capability on diameter transports.</p> <funcs> <func> -<name>start({Type, Ref}, Svc, [Opt]) +<name since="OTP R14B03">start({Type, Ref}, Svc, [Opt]) -> {ok, Pid} | {ok, Pid, [LAddr]} | {error, Reason}</name> diff --git a/lib/diameter/doc/src/diameter_transport.xml b/lib/diameter/doc/src/diameter_transport.xml index 294e8a8864..67fd54bc56 100644 --- a/lib/diameter/doc/src/diameter_transport.xml +++ b/lib/diameter/doc/src/diameter_transport.xml @@ -43,7 +43,7 @@ limitations under the License. <file>diameter_transport.xml</file> </header> -<module>diameter_transport</module> +<module since="OTP R14B03">diameter_transport</module> <modulesummary>Diameter transport interface.</modulesummary> <description> @@ -94,7 +94,7 @@ and has the binary() to send in its <c>bin</c> field.</p> <funcs> <func> -<name>Mod:start({Type, Ref}, Svc, Config) +<name since="OTP R14B03">Mod:start({Type, Ref}, Svc, Config) -> {ok, Pid} | {ok, Pid, LAddrs} | {error, Reason}</name> diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml index f2c7889e58..790a2f4e26 100644 --- a/lib/eldap/doc/src/eldap.xml +++ b/lib/eldap/doc/src/eldap.xml @@ -28,7 +28,7 @@ <date>2000-06-20</date> <rev>B</rev> </header> - <module>eldap</module> + <module since="OTP R15B01">eldap</module> <modulesummary>LDAP Client</modulesummary> <description> <p>This module provides a client api to the Lightweight Directory Access Protocol (LDAP). @@ -103,7 +103,7 @@ <funcs> <func> - <name>open([Host]) -> {ok, Handle} | {error, Reason}</name> + <name since="OTP R15B01">open([Host]) -> {ok, Handle} | {error, Reason}</name> <fsummary>Open a connection to an LDAP server.</fsummary> <type> <v>Handle = handle()</v> @@ -113,7 +113,7 @@ </desc> </func> <func> - <name>open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name> + <name since="OTP R15B01">open([Host], [Option]) -> {ok, Handle} | {error, Reason}</name> <fsummary>Open a connection to an LDAP server.</fsummary> <type> <v>Handle = handle()</v> @@ -129,7 +129,7 @@ </desc> </func> <func> - <name>close(Handle) -> ok</name> + <name since="OTP R15B01">close(Handle) -> ok</name> <fsummary>Shutdown the connection.</fsummary> <type> <v>Handle = handle()</v> @@ -140,14 +140,14 @@ </desc> </func> <func> - <name>start_tls(Handle, Options) -> return_value()</name> + <name since="OTP R16B03">start_tls(Handle, Options) -> return_value()</name> <fsummary>Upgrade a connection to TLS.</fsummary> <desc> <p>Same as start_tls(Handle, Options, infinity)</p> </desc> </func> <func> - <name>start_tls(Handle, Options, Timeout) -> return_value()</name> + <name since="OTP R16B03">start_tls(Handle, Options, Timeout) -> return_value()</name> <fsummary>Upgrade a connection to TLS.</fsummary> <type> <v>Handle = handle()</v> @@ -176,7 +176,7 @@ </desc> </func> <func> - <name>simple_bind(Handle, Dn, Password) -> return_value()</name> + <name since="OTP R15B01">simple_bind(Handle, Dn, Password) -> return_value()</name> <fsummary>Authenticate the connection.</fsummary> <type> <v>Handle = handle()</v> @@ -188,7 +188,7 @@ </desc> </func> <func> - <name>add(Handle, Dn, [Attribute]) -> return_value()</name> + <name since="OTP R15B01">add(Handle, Dn, [Attribute]) -> return_value()</name> <fsummary>Add an entry.</fsummary> <type> <v>Handle = handle()</v> @@ -209,7 +209,7 @@ </desc> </func> <func> - <name>delete(Handle, Dn) -> return_value()</name> + <name since="OTP R15B01">delete(Handle, Dn) -> return_value()</name> <fsummary>Delete an entry.</fsummary> <type> <v>Dn = string()</v> @@ -223,7 +223,7 @@ </func> <func> - <name>mod_add(Type, [Value]) -> modify_op()</name> + <name since="OTP R15B01">mod_add(Type, [Value]) -> modify_op()</name> <fsummary>Create a modification operation.</fsummary> <type> <v>Type = string()</v> @@ -232,7 +232,7 @@ <desc> <p> Create an add modification operation.</p> </desc> </func> <func> - <name>mod_delete(Type, [Value]) -> modify_op()</name> + <name since="OTP R15B01">mod_delete(Type, [Value]) -> modify_op()</name> <fsummary>Create a modification operation.</fsummary> <type> <v>Type = string()</v> @@ -241,7 +241,7 @@ <desc> <p> Create a delete modification operation.</p> </desc> </func> <func> - <name>mod_replace(Type, [Value]) -> modify_op()</name> + <name since="OTP R15B01">mod_replace(Type, [Value]) -> modify_op()</name> <fsummary>Create a modification operation.</fsummary> <type> <v>Type = string()</v> @@ -251,7 +251,7 @@ </func> <func> - <name>modify(Handle, Dn, [ModifyOp]) -> return_value()</name> + <name since="OTP R15B01">modify(Handle, Dn, [ModifyOp]) -> return_value()</name> <fsummary>Modify an entry.</fsummary> <type> <v>Dn = string()</v> @@ -267,7 +267,7 @@ </desc> </func> <func> - <name>modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name> + <name since="OTP 18.0">modify_password(Handle, Dn, NewPasswd) -> return_value() | {ok, GenPasswd}</name> <fsummary>Modify the password of a user.</fsummary> <type> <v>Dn = string()</v> @@ -278,7 +278,7 @@ </desc> </func> <func> - <name>modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name> + <name since="OTP 18.0">modify_password(Handle, Dn, NewPasswd, OldPasswd) -> return_value() | {ok, GenPasswd}</name> <fsummary>Modify the password of a user.</fsummary> <type> <v>Dn = string()</v> @@ -307,7 +307,7 @@ </desc> </func> <func> - <name>modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name> + <name since="OTP R15B01">modify_dn(Handle, Dn, NewRDN, DeleteOldRDN, NewSupDN) -> return_value()</name> <fsummary>Modify the DN of an entry.</fsummary> <type> <v>Dn = string()</v> @@ -327,7 +327,7 @@ </desc> </func> <func> - <name>search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name> + <name since="OTP R15B01">search(Handle, SearchOptions) -> {ok, #eldap_search_result{}} | {ok, {referral,referrals()}} | {error, Reason}</name> <fsummary>Search the Directory</fsummary> <type> <v>SearchOptions = #eldap_search{} | [SearchOption]</v> @@ -354,44 +354,44 @@ </func> <func> - <name>baseObject() -> scope()</name> + <name since="OTP R15B01">baseObject() -> scope()</name> <fsummary>Create search scope.</fsummary> <desc> <p> Search baseobject only.</p> </desc> </func> <func> - <name>singleLevel() -> scope()</name> + <name since="OTP R15B01">singleLevel() -> scope()</name> <fsummary>Create search scope.</fsummary> <desc> <p> Search the specified level only, i.e. do not recurse.</p> </desc> </func> <func> - <name>wholeSubtree() -> scope()</name> + <name since="OTP R15B01">wholeSubtree() -> scope()</name> <fsummary>Create search scope.</fsummary> <desc> <p> Search the entire subtree.</p> </desc> </func> <func> - <name>neverDerefAliases() -> dereference()</name> + <name since="OTP R15B01">neverDerefAliases() -> dereference()</name> <fsummary>Create search option.</fsummary> <desc> <p>Never derefrence aliases, treat aliases as entries.</p> </desc> </func> <func> - <name>derefAlways() -> dereference()</name> + <name since="OTP R15B01">derefAlways() -> dereference()</name> <fsummary>Create search option.</fsummary> <desc> <p>Always derefrence aliases.</p> </desc> </func> <func> - <name>derefInSearching() -> dereference()</name> + <name since="OTP R15B01">derefInSearching() -> dereference()</name> <fsummary>Create search option.</fsummary> <desc> <p>Derefrence aliases only when searching.</p> </desc> </func> <func> - <name>derefFindingBaseObj() -> dereference()</name> + <name since="OTP R15B01">derefFindingBaseObj() -> dereference()</name> <fsummary>Create search option.</fsummary> <desc> <p>Derefrence aliases only in finding the base.</p> </desc> </func> <func> - <name>present(Type) -> filter()</name> + <name since="OTP R15B01">present(Type) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Type = string()</v> @@ -399,7 +399,7 @@ <desc> <p>Create a filter which filters on attribute type presence.</p> </desc> </func> <func> - <name>substrings(Type, [SubString]) -> filter()</name> + <name since="OTP R15B01">substrings(Type, [SubString]) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Type = string()</v> @@ -409,7 +409,7 @@ <desc> <p>Create a filter which filters on substrings.</p> </desc> </func> <func> - <name>equalityMatch(Type, Value) -> filter()</name> + <name since="OTP R15B01">equalityMatch(Type, Value) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Type = string()</v> @@ -418,7 +418,7 @@ <desc> <p>Create a equality filter.</p> </desc> </func> <func> - <name>greaterOrEqual(Type, Value) -> filter()</name> + <name since="OTP R15B01">greaterOrEqual(Type, Value) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Type = string()</v> @@ -427,7 +427,7 @@ <desc> <p>Create a greater or equal filter.</p> </desc> </func> <func> - <name>lessOrEqual(Type, Value) -> filter()</name> + <name since="OTP R15B01">lessOrEqual(Type, Value) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Type = string()</v> @@ -436,7 +436,7 @@ <desc> <p>Create a less or equal filter.</p> </desc> </func> <func> - <name>approxMatch(Type, Value) -> filter()</name> + <name since="OTP R15B01">approxMatch(Type, Value) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Type = string()</v> @@ -445,7 +445,7 @@ <desc> <p>Create a approximation match filter.</p> </desc> </func> <func> - <name>extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name> + <name since="OTP 17.4">extensibleMatch(MatchValue, OptionalAttrs) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>MatchValue = string()</v> @@ -459,7 +459,7 @@ <p>creates a filter which performs a <c>caseExactMatch</c> on the attribute <c>sn</c> and matches with the value <c>"Bar"</c>. The default value of <c>dnAttributes</c> is <c>false</c>.</p> </desc> </func> <func> - <name>'and'([Filter]) -> filter()</name> + <name since="OTP R15B01">'and'([Filter]) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Filter = filter()</v> @@ -467,7 +467,7 @@ <desc> <p>Creates a filter where all <c>Filter</c> must be true.</p> </desc> </func> <func> - <name>'or'([Filter]) -> filter()</name> + <name since="OTP R15B01">'or'([Filter]) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Filter = filter()</v> @@ -475,7 +475,7 @@ <desc> <p>Create a filter where at least one of the <c>Filter</c> must be true.</p> </desc> </func> <func> - <name>'not'(Filter) -> filter()</name> + <name since="OTP R15B01">'not'(Filter) -> filter()</name> <fsummary>Create search filter option.</fsummary> <type> <v>Filter = filter()</v> diff --git a/lib/erl_docgen/doc/src/docgen_xml_check.xml b/lib/erl_docgen/doc/src/docgen_xml_check.xml index 68253edef7..8d6dceef43 100644 --- a/lib/erl_docgen/doc/src/docgen_xml_check.xml +++ b/lib/erl_docgen/doc/src/docgen_xml_check.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>docgen_xml_check</module> + <module since="OTP R15B">docgen_xml_check</module> <modulesummary>Validate XML documentation source code</modulesummary> <description> <p><c>docgen_xml_check</c> contains functions for validating XML @@ -39,7 +39,7 @@ <funcs> <func> - <name>validate(File) -> ok | error | {error, badfile}</name> + <name since="OTP R15B">validate(File) -> ok | error | {error, badfile}</name> <fsummary>Validate XML source code.</fsummary> <type> <v>File = string()</v> diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css index 34c6befb0e..89b278215c 100644 --- a/lib/erl_docgen/priv/css/otp_doc.css +++ b/lib/erl_docgen/priv/css/otp_doc.css @@ -71,6 +71,33 @@ a:visited { color: #1b6ec2; text-decoration: none } } .bold_code { font-family: mono, Courier, monospace; font-weight: bold } + +/* Invisible table for function specs, + * just to get since-version out in right margin */ +.func-table, .func-tr, .func-td, .func-since-td { + width: 200%; + border: 0; + padding: 0; + margin: 0; +} + +.func-tr:nth-child(n) { + background: inherit /* turn off zebra striped rows */ +} + +.func-td { + width: 50% +} + +.func-since-td { + width: 50%; + padding-left: 1em +} + +.func-td:hover { + background-color: #f5f5f5; +} + .code { font-family: mono, Courier, monospace; font-weight: normal; @@ -283,3 +310,9 @@ a > .code { .func-types-title{ font-size: 1em; } + +.since{ + color: gray; + font-weight: normal; + font-size: small; +}
\ No newline at end of file diff --git a/lib/erl_docgen/priv/dtd/cref.dtd b/lib/erl_docgen/priv/dtd/cref.dtd index 5ccd98ed89..d392081807 100644 --- a/lib/erl_docgen/priv/dtd/cref.dtd +++ b/lib/erl_docgen/priv/dtd/cref.dtd @@ -30,6 +30,8 @@ <!-- `name' is used in common.refs.dtd and must therefore be defined in each *ref. dtd --> <!ELEMENT name (ret,nametext) > +<!ATTLIST name since CDATA #IMPLIED> + <!ELEMENT ret (#PCDATA) > <!ELEMENT nametext (#PCDATA) > diff --git a/lib/erl_docgen/priv/dtd/erlref.dtd b/lib/erl_docgen/priv/dtd/erlref.dtd index 78d6771f52..8202ea5a4d 100644 --- a/lib/erl_docgen/priv/dtd/erlref.dtd +++ b/lib/erl_docgen/priv/dtd/erlref.dtd @@ -25,6 +25,7 @@ <!ELEMENT erlref (header,module,modulesummary,description, (section|funcs|datatypes)*,authors?) > <!ELEMENT module (#PCDATA) > +<!ATTLIST module since CDATA #IMPLIED> <!ELEMENT modulesummary (#PCDATA) > <!-- `name' is used in common.refs.dtd and must therefore @@ -34,4 +35,5 @@ arity CDATA #IMPLIED clause_i CDATA #IMPLIED anchor CDATA #IMPLIED + since CDATA #IMPLIED n_vars CDATA #IMPLIED> diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index a0a922216b..c5150d447c 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -191,6 +191,7 @@ <xsl:variable name="name" select="@name"/> <xsl:variable name="arity" select="@arity"/> <xsl:variable name="anchor" select="@anchor"/> + <xsl:variable name="since" select="@since"/> <xsl:variable name="spec0"> <xsl:call-template name="find_spec"/> </xsl:variable> @@ -225,11 +226,12 @@ <xsl:variable name="global_types" select="ancestor::erlref/datatypes"/> <xsl:variable name="local_types" select="../type[string-length(@name) > 0]"/> - <xsl:apply-templates select="$spec/contract/clause/head"> + <xsl:apply-templates select="$spec/contract/clause/head"> <xsl:with-param name="ghlink" select="ancestor-or-self::*[@ghlink]/@ghlink"/> <xsl:with-param name="local_types" select="$local_types"/> <xsl:with-param name="global_types" select="$global_types"/> - </xsl:apply-templates> + <xsl:with-param name="since" select="$since"/> + </xsl:apply-templates> </xsl:when> </xsl:choose> </xsl:template> @@ -238,19 +240,32 @@ <xsl:param name="ghlink"/> <xsl:param name="local_types"/> <xsl:param name="global_types"/> + <xsl:param name="since"/> <xsl:variable name="id" select="concat(concat(concat(concat(../../../name,'-'),../../../arity),'-'),generate-id(.))"/> - <div class="bold_code func-head" - onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';" - onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';"> - <xsl:call-template name="ghlink"> - <xsl:with-param name="ghlink" select="$ghlink"/> - <xsl:with-param name="id" select="$id"/> - </xsl:call-template> - <xsl:apply-templates mode="local_type"> - <xsl:with-param name="local_types" select="$local_types"/> - <xsl:with-param name="global_types" select="$global_types"/> - </xsl:apply-templates> - </div> + <table class="func-table"> + <tr class="func-tr"> + <td class="func-td"> + <div class="bold_code func-head" + onMouseOver="document.getElementById('ghlink-{$id}').style.visibility = 'visible';" + onMouseOut="document.getElementById('ghlink-{$id}').style.visibility = 'hidden';"> + <xsl:call-template name="ghlink"> + <xsl:with-param name="ghlink" select="$ghlink"/> + <xsl:with-param name="id" select="$id"/> + </xsl:call-template> + <xsl:apply-templates mode="local_type"> + <xsl:with-param name="local_types" select="$local_types"/> + <xsl:with-param name="global_types" select="$global_types"/> + </xsl:apply-templates> + </div> + </td> + <td class="func-since-td"> + <xsl:if test="string-length($since) > 0"> + <span class="since"><xsl:value-of select="$since"/> + </span> + </xsl:if> + </td> + </tr> + </table> </xsl:template> <!-- The *last* <name name="..." arity=".."/> --> @@ -1884,6 +1899,16 @@ <xsl:with-param name="partnum" select="$partnum"/> </xsl:apply-templates> </div> + <!-- Since --> + <xsl:if test="string-length(../module/@since) > 0"> + <xsl:call-template name="h3_title_link"> + <xsl:with-param name="title">Since</xsl:with-param> + </xsl:call-template> + <div class="REFBODY module-since"> + Module <xsl:value-of select="../module"/> was introduced in + <xsl:value-of select="../module/@since"/>. + </div> + </xsl:if> </xsl:template> <!-- Lib --> @@ -2030,11 +2055,10 @@ <xsl:template match="func"> <xsl:param name="partnum"/> - <p><xsl:apply-templates select="name"/> - <xsl:apply-templates - select="name[string-length(@arity) > 0 and position()=last()]" - mode="types"/> - </p> + <xsl:apply-templates select="name"/> + <xsl:apply-templates + select="name[string-length(@arity) > 0 and position()=last()]" + mode="types"/> <xsl:apply-templates select="fsummary|type|desc"> <xsl:with-param name="partnum" select="$partnum"/> @@ -2093,19 +2117,29 @@ <xsl:choose> <xsl:when test="ancestor::cref"> - <span class="bold_code bc-7"> - <xsl:call-template name="title_link"> - <xsl:with-param name="link" select="substring-before(nametext, '(')"/> - <xsl:with-param name="title"> - <xsl:value-of select="ret"/> - <xsl:call-template name="maybe-space-after-ret"> - <xsl:with-param name="s" select="ret"/> - </xsl:call-template> - <xsl:value-of select="nametext"/> - </xsl:with-param> - </xsl:call-template> - </span> - <br/> + <table class="func-table"> + <tr class="func-tr"> + <td class="func-td"> + <span class="bold_code bc-7"> + <xsl:call-template name="title_link"> + <xsl:with-param name="link" select="substring-before(nametext, '(')"/> + <xsl:with-param name="title"> + <xsl:value-of select="ret"/> + <xsl:call-template name="maybe-space-after-ret"> + <xsl:with-param name="s" select="ret"/> + </xsl:call-template> + <xsl:value-of select="nametext"/> + </xsl:with-param> + </xsl:call-template> + </span> + </td> + <td class="func-since-td"> + <xsl:if test="string-length(@since) > 0"> + <span class="since"><xsl:value-of select="@since"/></span> + </xsl:if> + </td> + </tr> + </table> </xsl:when> <xsl:when test="ancestor::erlref"> <xsl:variable name="fname"> @@ -2136,14 +2170,25 @@ </div> </xsl:when> <xsl:otherwise> - <div class="bold_code fun-type"> - <xsl:call-template name="title_link"> - <xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/> - <xsl:with-param name="title"> - <xsl:apply-templates/> - </xsl:with-param> - </xsl:call-template> - </div> + <table class="func-table"> + <tr class="func-tr"> + <td class="func-td"> + <div class="bold_code fun-type"> + <xsl:call-template name="title_link"> + <xsl:with-param name="link" select="concat(concat($fname,'-'),$arity)"/> + <xsl:with-param name="title"> + <xsl:apply-templates/> + </xsl:with-param> + </xsl:call-template> + </div> + </td> + <td class="func-since-td"> + <xsl:if test="string-length(@since) > 0"> + <span class="since"><xsl:value-of select="@since"/></span> + </xsl:if> + </td> + </tr> + </table> </xsl:otherwise> </xsl:choose> </xsl:when> diff --git a/lib/erl_interface/configure.in b/lib/erl_interface/configure.in index 46dd995289..14f06f946f 100644 --- a/lib/erl_interface/configure.in +++ b/lib/erl_interface/configure.in @@ -77,6 +77,15 @@ AC_ARG_ENABLE(threads, esac ], [ threads_disabled=maybe ]) +AC_ARG_ENABLE(mask-real-errno, +[ --disable-mask-real-errno do not mask real 'errno'], +[ case "$enableval" in + no) mask_real_errno=no ;; + *) mask_real_errno=yes ;; + esac ], +[ mask_real_errno=yes ]) + + dnl ---------------------------------------------------------------------- dnl Checks for programs dnl ---------------------------------------------------------------------- @@ -95,6 +104,10 @@ AC_CHECK_SIZEOF(long) AC_CHECK_SIZEOF(void *) AC_CHECK_SIZEOF(long long) +if test $mask_real_errno = yes; then + AC_DEFINE(EI_HIDE_REAL_ERRNO, 1, [Define if 'errno' should not be exposed as is in 'erl_errno']) +fi + dnl We set EI_64BIT mode when long is 8 bytes, this makes things dnl work on windows and unix correctly if test $ac_cv_sizeof_long = 8; then @@ -153,7 +166,7 @@ AC_CHECK_LIB([socket], [getpeername]) # Checks for header files. AC_HEADER_STDC AC_HEADER_SYS_WAIT -AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/param.h sys/socket.h sys/select.h sys/time.h unistd.h sys/types.h]) +AC_CHECK_HEADERS([arpa/inet.h fcntl.h limits.h malloc.h netdb.h netinet/in.h stddef.h stdlib.h string.h sys/param.h sys/socket.h sys/select.h sys/time.h unistd.h sys/types.h sys/uio.h]) # Checks for typedefs, structures, and compiler characteristics. # fixme AC_C_CONST & AC_C_VOLATILE needed for Windows? @@ -188,7 +201,7 @@ AC_CHECK_FUNCS([dup2 gethostbyaddr gethostbyname \ gethostbyaddr_r \ gethostbyname_r gethostname writev \ gethrtime gettimeofday inet_ntoa memchr memmove memset select \ - socket strchr strerror strrchr strstr uname]) + socket strchr strerror strrchr strstr uname sysconf]) AC_CHECK_FUNC(res_gethostbyname, [], AC_CHECK_LIB(resolv, res_gethostbyname) ) @@ -250,6 +263,7 @@ AC_SUBST(EI_THREADS) case "$threads_disabled" in no|maybe) LM_CHECK_THR_LIB + ETHR_CHK_GCC_ATOMIC_OPS([]) case "$THR_LIB_NAME" in "") @@ -263,7 +277,7 @@ case "$threads_disabled" in EI_THREADS="true" THR_DEFS="$THR_DEFS -D_WIN32_WINNT=0x0600 -DWINVER=0x0600" ;; - pthread) + pthread) EI_THREADS="true" ;; *) diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml index 9502fb1ee7..16f4e18637 100644 --- a/lib/erl_interface/doc/src/ei.xml +++ b/lib/erl_interface/doc/src/ei.xml @@ -124,7 +124,7 @@ typedef enum { <funcs> <func> - <name><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_atom(const char *buf, int *index, char *p)</nametext></name> <fsummary>Decode an atom.</fsummary> <desc> <p>Decodes an atom from the binary format. The <c>NULL</c>-terminated @@ -134,7 +134,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name> + <name since="OTP R16B"><ret>int</ret><nametext>ei_decode_atom_as(const char *buf, int *index, char *p, int plen, erlang_char_encoding want, erlang_char_encoding* was, erlang_char_encoding* result)</nametext></name> <fsummary>Decode an atom.</fsummary> <desc> <p>Decodes an atom from the binary format. The <c>NULL</c>-terminated @@ -158,7 +158,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_bignum(const char *buf, int *index, mpz_t obj)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_bignum(const char *buf, int *index, mpz_t obj)</nametext></name> <fsummary>Decode a GMP arbitrary precision integer.</fsummary> <desc> <p>Decodes an integer in the binary format to a GMP @@ -168,7 +168,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_binary(const char *buf, int *index, void *p, long *len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_binary(const char *buf, int *index, void *p, long *len)</nametext></name> <fsummary>Decode a binary.</fsummary> <desc> <p>Decodes a binary from the binary format. Parameter @@ -180,7 +180,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_boolean(const char *buf, int *index, int *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_boolean(const char *buf, int *index, int *p)</nametext></name> <fsummary>Decode a boolean.</fsummary> <desc> <p>Decodes a boolean value from the binary format. @@ -190,7 +190,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_char(const char *buf, int *index, char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_char(const char *buf, int *index, char *p)</nametext></name> <fsummary>Decode an 8-bit integer between 0-255.</fsummary> <desc> <p>Decodes a char (8-bit) integer between 0-255 from the binary format. @@ -203,7 +203,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_double(const char *buf, int *index, double *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_double(const char *buf, int *index, double *p)</nametext></name> <fsummary>Decode a double.</fsummary> <desc> <p>Decodes a double-precision (64-bit) floating @@ -212,7 +212,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_ei_term(const char* buf, int* index, ei_term* term)</nametext></name> <fsummary>Decode a term, without previous knowledge of type.</fsummary> <desc> <p>Decodes any term, or at least tries to. If the term @@ -233,8 +233,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name> - <name><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_fun(const char *buf, int *index, erlang_fun *p)</nametext></name> + <name since=""><ret>void</ret><nametext>free_fun(erlang_fun* f)</nametext></name> <fsummary>Decode a fun.</fsummary> <desc> <p>Decodes a fun from the binary format. Parameter @@ -248,7 +248,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_list_header(const char *buf, int *index, int *arity)</nametext></name> <fsummary>Decode a list.</fsummary> <desc> <p>Decodes a list header from the binary @@ -265,7 +265,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_long(const char *buf, int *index, long *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_long(const char *buf, int *index, long *p)</nametext></name> <fsummary>Decode integer.</fsummary> <desc> <p>Decodes a long integer from the binary format. @@ -275,7 +275,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_longlong(const char *buf, int *index, long long *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_longlong(const char *buf, int *index, long long *p)</nametext></name> <fsummary>Decode integer.</fsummary> <desc> <p>Decodes a GCC <c>long long</c> or Visual C++ @@ -286,7 +286,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_map_header(const char *buf, int *index, int *arity)</nametext></name> + <name since="OTP 17.0"><ret>int</ret><nametext>ei_decode_map_header(const char *buf, int *index, int *arity)</nametext></name> <fsummary>Decode a map.</fsummary> <desc> <p>Decodes a map header from the binary @@ -299,7 +299,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_pid(const char *buf, int *index, erlang_pid *p)</nametext></name> <fsummary>Decode a <c>pid</c>.</fsummary> <desc> <p>Decodes a process identifier (pid) from the binary format.</p> @@ -307,7 +307,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_port(const char *buf, int *index, erlang_port *p)</nametext></name> <fsummary>Decode a port.</fsummary> <desc> <p>Decodes a port identifier from the binary format.</p> @@ -315,7 +315,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_ref(const char *buf, int *index, erlang_ref *p)</nametext></name> <fsummary>Decode a reference.</fsummary> <desc> <p>Decodes a reference from the binary format.</p> @@ -323,7 +323,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_string(const char *buf, int *index, char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_string(const char *buf, int *index, char *p)</nametext></name> <fsummary>Decode a string.</fsummary> <desc> <p>Decodes a string from the binary format. A @@ -338,7 +338,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_term(const char *buf, int *index, void *t)</nametext></name> <fsummary>Decode a <c>ETERM</c>.</fsummary> <desc> <p>Decodes a term from the binary format. The term @@ -352,7 +352,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_trace(const char *buf, int *index, erlang_trace *p)</nametext></name> <fsummary>Decode a trace token.</fsummary> <desc> <p>Decodes an Erlang trace token from the binary format.</p> @@ -360,7 +360,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_tuple_header(const char *buf, int *index, int *arity)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_tuple_header(const char *buf, int *index, int *arity)</nametext></name> <fsummary>Decode a tuple.</fsummary> <desc> <p>Decodes a tuple header, the number of elements @@ -370,7 +370,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_ulong(const char *buf, int *index, unsigned long *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_ulong(const char *buf, int *index, unsigned long *p)</nametext></name> <fsummary>Decode unsigned integer.</fsummary> <desc> <p>Decodes an unsigned long integer from the binary format. @@ -380,7 +380,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_ulonglong(const char *buf, int *index, unsigned long long *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_ulonglong(const char *buf, int *index, unsigned long long *p)</nametext></name> <fsummary>Decode unsigned integer.</fsummary> <desc> <p>Decodes a GCC <c>unsigned long long</c> or Visual C++ @@ -390,7 +390,7 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_decode_version(const char *buf, int *index, int *version)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_decode_version(const char *buf, int *index, int *version)</nametext></name> <fsummary>Decode an empty list (<c>nil</c>).</fsummary> <desc> <p>Decodes the version magic number for the @@ -400,10 +400,10 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_atom(char *buf, int *index, const char *p)</nametext></name> - <name><ret>int</ret><nametext>ei_encode_atom_len(char *buf, int *index, const char *p, int len)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_atom(char *buf, int *index, const char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_atom_len(char *buf, int *index, const char *p, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_atom(ei_x_buff* x, const char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_atom_len(ei_x_buff* x, const char *p, int len)</nametext></name> <fsummary>Encode an atom.</fsummary> <desc> <p>Encodes an atom in the binary format. Parameter <c>p</c> @@ -415,10 +415,10 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_atom_as(char *buf, int *index, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> - <name><ret>int</ret><nametext>ei_encode_atom_len_as(char *buf, int *index, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> + <name since="OTP R16B"><ret>int</ret><nametext>ei_encode_atom_as(char *buf, int *index, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> + <name since="OTP R16B"><ret>int</ret><nametext>ei_encode_atom_len_as(char *buf, int *index, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> + <name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_as(ei_x_buff* x, const char *p, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> + <name since="OTP R16B"><ret>int</ret><nametext>ei_x_encode_atom_len_as(ei_x_buff* x, const char *p, int len, erlang_char_encoding from_enc, erlang_char_encoding to_enc)</nametext></name> <fsummary>Encode an atom.</fsummary> <desc> <p>Encodes an atom in the binary format. Parameter <c>p</c> is the name of the atom with @@ -435,8 +435,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_bignum(char *buf, int *index, mpz_t obj)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_bignum(ei_x_buff *x, mpz_t obj)</nametext></name> <fsummary>Encode an arbitrary precision integer.</fsummary> <desc> <p>Encodes a GMP <c>mpz_t</c> integer to binary format. @@ -446,8 +446,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_binary(char *buf, int *index, const void *p, long len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_binary(ei_x_buff* x, const void *p, long len)</nametext></name> <fsummary>Encode a binary.</fsummary> <desc> <p>Encodes a binary in the binary format. The data is at @@ -456,8 +456,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_boolean(char *buf, int *index, int p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_boolean(ei_x_buff* x, int p)</nametext></name> <fsummary>Encode a boolean.</fsummary> <desc> <p>Encodes a boolean value as the atom <c>true</c> if @@ -467,8 +467,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_char(char *buf, int *index, char p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_char(ei_x_buff* x, char p)</nametext></name> <fsummary>Encode an 8-bit integer between 0-255.</fsummary> <desc> <p>Encodes a char (8-bit) as an integer between 0-255 in the binary @@ -481,8 +481,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_double(char *buf, int *index, double p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_double(ei_x_buff* x, double p)</nametext></name> <fsummary>Encode a double float.</fsummary> <desc> <p>Encodes a double-precision (64-bit) floating point number in @@ -493,8 +493,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_empty_list(char* buf, int* index)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_empty_list(ei_x_buff* x)</nametext></name> <fsummary>Encode an empty list (<c>nil</c>).</fsummary> <desc> <p>Encodes an empty list. It is often used at the tail of a list.</p> @@ -502,8 +502,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_fun(char *buf, int *index, const erlang_fun *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_fun(ei_x_buff* x, const erlang_fun* fun)</nametext></name> <fsummary>Encode a fun.</fsummary> <desc> <p>Encodes a fun in the binary format. Parameter <c>p</c> @@ -515,8 +515,8 @@ typedef enum { </func> <func> - <name><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_list_header(char *buf, int *index, int arity)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_list_header(ei_x_buff* x, int arity)</nametext></name> <fsummary>Encode a list.</fsummary> <desc> <p>Encodes a list header, with a specified @@ -552,8 +552,8 @@ ei_x_encode_empty_list(&x);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_long(char *buf, int *index, long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_long(ei_x_buff* x, long p)</nametext></name> <fsummary>Encode integer.</fsummary> <desc> <p>Encodes a long integer in the binary format. @@ -563,8 +563,8 @@ ei_x_encode_empty_list(&x);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_longlong(char *buf, int *index, long long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_longlong(ei_x_buff* x, long long p)</nametext></name> <fsummary>Encode integer.</fsummary> <desc> <p>Encodes a GCC <c>long long</c> or Visual C++ @@ -574,8 +574,8 @@ ei_x_encode_empty_list(&x);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name> + <name since="OTP 17.0"><ret>int</ret><nametext>ei_encode_map_header(char *buf, int *index, int arity)</nametext></name> + <name since="OTP 17.0"><ret>int</ret><nametext>ei_x_encode_map_header(ei_x_buff* x, int arity)</nametext></name> <fsummary>Encode a map.</fsummary> <desc> <p>Encodes a map header, with a specified arity. The next @@ -595,8 +595,8 @@ ei_x_encode_string(&x, "Banana");</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_pid(char *buf, int *index, const erlang_pid *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_pid(ei_x_buff* x, const erlang_pid *p)</nametext></name> <fsummary>Encode a pid.</fsummary> <desc> <p>Encodes an Erlang process identifier (pid) in the binary @@ -607,8 +607,8 @@ ei_x_encode_string(&x, "Banana");</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_port(char *buf, int *index, const erlang_port *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_port(ei_x_buff* x, const erlang_port *p)</nametext></name> <fsummary>Encode a port.</fsummary> <desc> <p>Encodes an Erlang port in the binary format. Parameter @@ -619,8 +619,8 @@ ei_x_encode_string(&x, "Banana");</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_ref(char *buf, int *index, const erlang_ref *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_ref(ei_x_buff* x, const erlang_ref *p)</nametext></name> <fsummary>Encode a ref.</fsummary> <desc> <p>Encodes an Erlang reference in the binary format. Parameter @@ -631,10 +631,10 @@ ei_x_encode_string(&x, "Banana");</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_string(char *buf, int *index, const char *p)</nametext></name> - <name><ret>int</ret><nametext>ei_encode_string_len(char *buf, int *index, const char *p, int len)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_string(char *buf, int *index, const char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_string_len(char *buf, int *index, const char *p, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_string(ei_x_buff* x, const char *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_string_len(ei_x_buff* x, const char* s, int len)</nametext></name> <fsummary>Encode a string.</fsummary> <desc> <p>Encodes a string in the binary format. (A string in Erlang @@ -645,8 +645,8 @@ ei_x_encode_string(&x, "Banana");</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_term(char *buf, int *index, void *t)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_term(ei_x_buff* x, void *t)</nametext></name> <fsummary>Encode an <c>erl_interface</c> term.</fsummary> <desc> <p>Encodes an <c>ETERM</c>, as obtained from @@ -656,8 +656,8 @@ ei_x_encode_string(&x, "Banana");</pre> </desc> </func> <func> - <name><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_trace(char *buf, int *index, const erlang_trace *p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_trace(ei_x_buff* x, const erlang_trace *p)</nametext></name> <fsummary>Encode a trace token.</fsummary> <desc> <p>Encodes an Erlang trace token in the binary format. @@ -668,8 +668,8 @@ ei_x_encode_string(&x, "Banana");</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_tuple_header(char *buf, int *index, int arity)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_tuple_header(ei_x_buff* x, int arity)</nametext></name> <fsummary>Encode a tuple.</fsummary> <desc> <p>Encodes a tuple header, with a specified @@ -687,8 +687,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_ulong(char *buf, int *index, unsigned long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_ulong(ei_x_buff* x, unsigned long p)</nametext></name> <fsummary>Encode unsigned integer.</fsummary> <desc> <p>Encodes an unsigned long integer in the binary format. @@ -698,8 +698,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_ulonglong(char *buf, int *index, unsigned long long p)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_ulonglong(ei_x_buff* x, unsigned long long p)</nametext></name> <fsummary>Encode unsigned integer.</fsummary> <desc> <p>Encodes a GCC <c>unsigned long long</c> or Visual C++ @@ -709,8 +709,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name> - <name><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_encode_version(char *buf, int *index)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_encode_version(ei_x_buff* x)</nametext></name> <fsummary>Encode version.</fsummary> <desc> <p>Encodes a version magic number for the binary format. Must @@ -719,7 +719,7 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_get_type(const char *buf, const int *index, int *type, int *size)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_get_type(const char *buf, const int *index, int *type, int *size)</nametext></name> <fsummary>Fetch the type and size of an encoded term.</fsummary> <desc> <p>Returns the type in <c>type</c> and size in @@ -733,8 +733,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_print_term(FILE* fp, const char* buf, int* index)</nametext></name> - <name><ret>int</ret><nametext>ei_s_print_term(char** s, const char* buf, int* index)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_print_term(FILE* fp, const char* buf, int* index)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_s_print_term(char** s, const char* buf, int* index)</nametext></name> <fsummary>Print a term in clear text.</fsummary> <desc> <p>Prints a term, in clear text, to the file @@ -759,7 +759,7 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name> + <name since=""><ret>void</ret><nametext>ei_set_compat_rel(release_number)</nametext></name> <fsummary>Set the ei library in compatibility mode.</fsummary> <type> <v>unsigned release_number;</v> @@ -794,7 +794,7 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_skip_term(const char* buf, int* index)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_skip_term(const char* buf, int* index)</nametext></name> <fsummary>Skip a term.</fsummary> <desc> <p>Skips a term in the specified buffer; @@ -815,8 +815,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name> - <name><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_append(ei_x_buff* x, const ei_x_buff* x2)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_append_buf(ei_x_buff* x, const char* buf, int len)</nametext></name> <fsummary>Append a buffer at the end.</fsummary> <desc> <p>Appends data at the end of buffer <c>x</c>.</p> @@ -824,8 +824,8 @@ ei_encode_tuple_header(buf, &i, 0);</pre> </func> <func> - <name><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name> - <name><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_format(ei_x_buff* x, const char* fmt, ...)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_format_wo_ver(ei_x_buff* x, const char *fmt, ... )</nametext></name> <fsummary>Format a term from a format string and parameters.</fsummary> <desc> <p>Formats a term, given as a string, to a buffer. @@ -853,7 +853,7 @@ encodes the tuple {numbers,12,3.14159}</pre> </func> <func> - <name><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_free(ei_x_buff* x)</nametext></name> <fsummary>Free a buffer.</fsummary> <desc> <p>Frees an <c>ei_x_buff</c> buffer. @@ -862,8 +862,8 @@ encodes the tuple {numbers,12,3.14159}</pre> </func> <func> - <name><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name> - <name><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_new(ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_x_new_with_version(ei_x_buff* x)</nametext></name> <fsummary>Allocate a new buffer.</fsummary> <desc> <p>Allocates a new <c>ei_x_buff</c> buffer. The diff --git a/lib/erl_interface/doc/src/ei_connect.xml b/lib/erl_interface/doc/src/ei_connect.xml index 607a7cbff4..e318dd6664 100644 --- a/lib/erl_interface/doc/src/ei_connect.xml +++ b/lib/erl_interface/doc/src/ei_connect.xml @@ -85,20 +85,288 @@ the <c>_tmo</c> suffix.</p> </section> + <section> + <marker id="ussi"/> + <title>User Supplied Socket Implementation</title> + <p>By default <c>ei</c> supplies a TCP/IPv4 socket interface + that is used when communicating. The user can however plug in + his/her own IPv4 socket implementation. This, for example, in order + to communicate over TLS. A user supplied socket implementation + is plugged in by passing a + <seealso marker="#ei_socket_callbacks">callback structure</seealso> + to either + <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso> + or + <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso>.</p> + + <p>All callbacks in the <c>ei_socket_callbacks</c> structure + <em>should</em> return zero on success; and a posix error + code on failure.</p> + + <p>The <c>addr</c> argument of the <c>listen</c>, <c>accept</c>, + and <c>connect</c> callbacks refer to appropriate address + structure for currently used protocol. Currently <c>ei</c> + only supports IPv4. That is, at this time <c>addr</c> always + points to a <c>struct sockaddr_in</c> structure.</p> + + <p>The <c>ei_socket_callbacks</c> structure may be enlarged in + the future. All fields not set, <em>needs</em> to be zeroed out.</p> + + <marker id="ei_socket_callbacks"/> + <code type="none"><![CDATA[ +typedef struct { + int flags; + int (*socket)(void **ctx, void *setup_ctx); + int (*close)(void *ctx); + int (*listen)(void *ctx, void *addr, int *len, int backlog); + int (*accept)(void **ctx, void *addr, int *len, unsigned tmo); + int (*connect)(void *ctx, void *addr, int len, unsigned tmo); + int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo); + int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo); + int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo); + int (*handshake_packet_header_size)(void *ctx, int *sz); + int (*connect_handshake_complete)(void *ctx); + int (*accept_handshake_complete)(void *ctx); + int (*get_fd)(void *ctx, int *fd); +} ei_socket_callbacks; + ]]></code> + + <taglist> + + <tag><c>flags</c></tag> + <item> + <p>Flags informing <c>ei</c> about the behaviour of the + callbacks. Flags should be bitwise or:ed together. If no flag, + is set, the <c>flags</c> field should contain <c>0</c>. Currently, + supported flags:</p> + <taglist> + <tag><c>EI_SCLBK_FLG_FULL_IMPL</c></tag> + <item> + <p> + If set, the <c>accept()</c>, <c>connect()</c>, + <c>writev()</c>, <c>write()</c>, and <c>read()</c> callbacks + implements timeouts. The timeout is passed in the <c>tmo</c> + argument and is given in milli seconds. Note that the + <c>tmo</c> argument to these callbacks differ from the + timeout arguments in the <c>ei</c> API. Zero means a zero + timeout. That is, poll and timeout immediately unless the + operation is successful. <c>EI_SCLBK_INF_TMO</c> + (max <c>unsigned</c>) means infinite timeout. The file + descriptor is in blocking mode when a callback is called, + and it must be in blocking mode when the callback returns. + </p> + <p> + If not set, <c>ei</c> will implement the timeout using + <c>select()</c> in order to determine when to call the + callbacks and when to time out. The <c>tmo</c> arguments + of the <c>accept()</c>, <c>connect()</c>, <c>writev()</c>, + <c>write()</c>, and <c>read()</c> callbacks should be + ignored. The callbacks may be called in non-blocking mode. + The callbacks are not allowed to change between blocking + and non-blocking mode. In order for this to work, + <c>select()</c> needs to interact with the socket primitives + used the same way as it interacts with the ordinary socket + primitives. If this is not the case, the callbacks + <em>need</em> to implement timeouts and this flag should + be set. + </p> + </item> + </taglist> + <p>More flags may be introduced in the future.</p> + </item> + + <tag><c>int (*socket)(void **ctx, void *setup_ctx)</c></tag> + <item> + <p>Create a socket and a context for the socket.</p> + + <p>On success it should set <c>*ctx</c> to point to a context for + the created socket. This context will be passed to all other + socket callbacks. This function will be passed the same + <c>setup_context</c> as passed to the preceeding + <seealso marker="#ei_connect_init"><c>ei_connect_init_ussi()</c></seealso> + or + <seealso marker="#ei_connect_init"><c>ei_connect_xinit_ussi()</c></seealso> + call.</p> + + <note><p>During the lifetime of a socket, the pointer <c>*ctx</c> + <em>has</em> to remain the same. That is, it cannot later be + relocated.</p></note> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*close)(void *ctx)</c></tag> + <item> + <p>Close the socket identified by <c>ctx</c> and destroy the context.</p> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*listen)(void *ctx, void *addr, int *len, int backlog)</c></tag> + <item> + <p>Bind the socket identified by <c>ctx</c> to a local interface + and then listen on it.</p> + + <p>The <c>addr</c> and <c>len</c> arguments are both input and output + arguments. When called <c>addr</c> points to an address structure of + lenght <c>*len</c> containing information on how to bind the socket. + Uppon return this callback should have updated the structure referred + by <c>addr</c> with information on how the socket actually was bound. + <c>*len</c> should be updated to reflect the size of <c>*addr</c> + updated. <c>backlog</c> identifies the size of the backlog for the + listen socket.</p> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*accept)(void **ctx, void *addr, int *len, unsigned tmo)</c></tag> + <item> + <p>Accept connections on the listen socket identified by + <c>*ctx</c>.</p> + + <p>When a connection is accepted, a new context for the accepted + connection should be created and <c>*ctx</c> should be updated + to point to the new context for the accepted connection. When + called <c>addr</c> points to an uninitialized address structure + of lenght <c>*len</c>. Uppon return this callback should have + updated this structure with information about the client address. + <c>*len</c> should be updated to reflect the size of <c>*addr</c> + updated. + </p> + + <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set, + <c>tmo</c> contains timeout time in milliseconds.</p> + + <note><p>During the lifetime of a socket, the pointer <c>*ctx</c> + <em>has</em> to remain the same. That is, it cannot later be + relocated.</p></note> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*connect)(void *ctx, void *addr, int len, unsigned tmo)</c></tag> + <item> + <p>Connect the socket identified by <c>ctx</c> to the address + identified by <c>addr</c>.</p> + + <p>When called <c>addr</c> points to an address structure of + lenght <c>len</c> containing information on where to connect.</p> + + <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set, + <c>tmo</c> contains timeout time in milliseconds.</p> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*writev)(void *ctx, const void *iov, long iovcnt, ssize_t *len, unsigned tmo)</c></tag> + <item> + <p>Write data on the connected socket identified by <c>ctx</c>.</p> + + <p><c>iov</c> points to an array of <c>struct iovec</c> structures of + length <c>iovcnt</c> containing data to write to the socket. On success, + this callback should set <c>*len</c> to the amount of bytes successfully + written on the socket.</p> + + <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set, + <c>tmo</c> contains timeout time in milliseconds.</p> + + <p>This callback is optional. Set the <c>writev</c> field + in the the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not + implemented.</p> + </item> + + <tag><c>int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo)</c></tag> + <item> + <p>Write data on the connected socket identified by <c>ctx</c>.</p> + + <p>When called <c>buf</c> points to a buffer of length <c>*len</c> + containing the data to write on the socket. On success, this callback + should set <c>*len</c> to the amount of bytes successfully written on + the socket.</p> + + <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set, + <c>tmo</c> contains timeout time in milliseconds.</p> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo)</c></tag> + <item> + <p>Read data on the connected socket identified by <c>ctx</c>.</p> + + <p><c>buf</c> points to a buffer of length <c>*len</c> where the + read data should be placed. On success, this callback should update + <c>*len</c> to the amount of bytes successfully read on the socket.</p> + + <p>If the <c>EI_SCLBK_FLG_FULL_IMPL</c> flag has been set, + <c>tmo</c> contains timeout time in milliseconds.</p> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*handshake_packet_header_size)(void *ctx, int *sz)</c></tag> + <item> + <p>Inform about handshake packet header size to use during the Erlang + distribution handshake.</p> + + <p>On success, <c>*sz</c> should be set to the handshake packet header + size to use. Valid values are <c>2</c> and <c>4</c>. Erlang TCP + distribution use a handshake packet size of <c>2</c> and Erlang TLS + distribution use a handshake packet size of <c>4</c>.</p> + + <p>This callback is mandatory.</p> + </item> + + <tag><c>int (*connect_handshake_complete)(void *ctx)</c></tag> + <item> + <p>Called when a locally started handshake has completed successfully.</p> + + <p>This callback is optional. Set the <c>connect_handshake_complete</c> field + in the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not implemented.</p> + </item> + + <tag><c>int (*accept_handshake_complete)(void *ctx)</c></tag> + <item> + <p>Called when a remotely started handshake has completed successfully.</p> + + <p>This callback is optional. Set the <c>accept_handshake_complete</c> field in + the <c>ei_socket_callbacks</c> structure to <c>NULL</c> if not implemented.</p> + </item> + + <tag><c>int (*get_fd)(void *ctx, int *fd)</c></tag> + <item> + <p>Inform about file descriptor used by the socket which is identified + by <c>ctx</c>.</p> + + <note><p>During the lifetime of a socket, the file descriptor + <em>has</em> to remain the same. That is, repeated calls to this + callback with the same context <c>should</c> always report the same + file descriptor.</p> + <p>The file descriptor <em>has</em> to be a real file descriptor. + That is, no other operation should be able to get the same file + descriptor until it has been released by the <c>close()</c> + callback.</p> + </note> + + <p>This callback is mandatory.</p> + </item> + </taglist> + </section> <funcs> <func> - <name><ret>struct hostent</ret><nametext>*ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name> - <name><ret>struct hostent</ret><nametext>*ei_gethostbyaddr_r(const char *addr, int length, int type, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name> - <name><ret>struct hostent</ret><nametext>*ei_gethostbyname(const char *name)</nametext></name> - <name><ret>struct hostent</ret><nametext>*ei_gethostbyname_r(const char *name, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr(const char *addr, int len, int type)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyaddr_r(const char *addr, int length, int type, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyname(const char *name)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>ei_gethostbyname_r(const char *name, struct hostent *hostp, char *buffer, int buflen, int *h_errnop)</nametext></name> <fsummary>Name lookup functions.</fsummary> <desc> <p>Convenience functions for some common name lookup functions.</p> </desc> </func> + <func> - <name><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_accept(ei_cnode *ec, int listensock, ErlConnect *conp)</nametext></name> <fsummary>Accept a connection from another node.</fsummary> <desc> <p>Used by a server process to accept a @@ -130,7 +398,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_accept_tmo(ei_cnode *ec, int listensock, ErlConnect *conp, unsigned timeout_ms)</nametext></name> <fsummary>Accept a connection from another node with optional time-out.</fsummary> <desc> @@ -141,8 +409,16 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name> - <name><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name> + <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_close_connection(int fd)</nametext></name> + <fsummary>Close a connection.</fsummary> + <desc> + <p>Closes a previously opened connection or listen socket.</p> + </desc> + </func> + + <func> + <name since=""><ret>int</ret><nametext>ei_connect(ei_cnode* ec, char *nodename)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename)</nametext></name> <fsummary>Establish a connection to an Erlang node.</fsummary> <desc> <p>Sets up a connection to an Erlang node.</p> @@ -192,8 +468,10 @@ fd = ei_xconnect(&ec, &addr, ALIVE); </func> <func> - <name><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name> - <name><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_connect_init(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation)</nametext></name> + <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_connect_xinit(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation)</nametext></name> + <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, const char *thisalivename, const char *thisnodename, Erl_IpAddr thisipaddr, const char *cookie, short creation, ei_socket_callbacks *cbs, int cbs_sz, void *setup_context)</nametext></name> <fsummary>Initialize for a connection.</fsummary> <desc> <p>Initializes the <c>ec</c> structure, to @@ -236,6 +514,21 @@ fd = ei_xconnect(&ec, &addr, ALIVE); <item> <p><c>thispaddr</c> if the IP address of the host.</p> </item> + <item> + <p><c>cbs</c> is a pointer to a + <seealso marker="#ei_socket_callbacks">callback structure</seealso> + implementing and alternative socket interface.</p> + </item> + <item> + <p><c>cbs_sz</c> is the size of the structure + pointed to by <c>cbs</c>.</p> + </item> + <item> + <p><c>setup_context</c> is a pointer to a structure that + will be passed as second argument to the <c>socket</c> callback + in the <c>cbs</c> structure.</p> + </item> + </list> <p>A C-node acting as a server is assigned a creation number when it calls <c>ei_publish()</c>.</p> @@ -273,8 +566,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name> - <name><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned timeout_ms)</nametext></name> <fsummary>Establish a connection to an Erlang node with optional time-out.</fsummary> <desc> @@ -286,8 +579,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_get_tracelevel(void)</nametext></name> - <name><ret>void</ret><nametext>ei_set_tracelevel(int level)</nametext></name> + <name since="OTP R13B04"><ret>int</ret><nametext>ei_get_tracelevel(void)</nametext></name> + <name since="OTP R13B04"><ret>void</ret><nametext>ei_set_tracelevel(int level)</nametext></name> <fsummary>Get and set functions for tracing.</fsummary> <desc> <p>Used to set tracing on the distribution. The levels are different @@ -299,7 +592,46 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name> + <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_listen(ei_cnode *ec, int *port, int backlog)</nametext></name> + <name since="OTP @OTP-15442@"><ret>int</ret><nametext>ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog)</nametext></name> + <fsummary>Create a listen socket.</fsummary> + <desc> + <p>Used by a server process to setup a listen socket which + later can be used for accepting connections from client processes. + </p> + <list type="bulleted"> + <item> + <p><c>ec</c> is the C-node structure.</p> + </item> + <item> + <p><c>adr</c> is local interface to bind to.</p> + </item> + <item> + <p><c>port</c> is a pointer to an integer containing the + port number to bind to. If <c>*port</c> equals <c>0</c> + when calling <c>ei_listen()</c>, the socket will be bound to + an ephemeral port. On success, <c>ei_listen()</c> will update + the value of <c>*port</c> to the port actually bound to. + </p> + </item> + <item> + <p><c>backlog</c> is maximum backlog of pending connections.</p> + </item> + </list> + <p><c>ei_listen</c> will create a socket, bind to a port on the + local interface identified by <c>adr</c> (or all local interfaces if + <c>ei_listen()</c> is called), and mark the socket as a passive socket + (that is, a socket that will be used for accepting incoming connections). + </p> + <p> + On success, a file descriptor is returned which can be used in a call to + <c>ei_accept()</c>. On failure, <c>ERL_ERROR</c> is returned and + <c>erl_errno</c> is set to <c>EIO</c>.</p> + </desc> + </func> + + <func> + <name since=""><ret>int</ret><nametext>ei_publish(ei_cnode *ec, int port)</nametext></name> <fsummary>Publish a node name.</fsummary> <desc> <p>Used by a server process to register @@ -336,7 +668,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_publish_tmo(ei_cnode *ec, int port, unsigned timeout_ms)</nametext></name> <fsummary>Publish a node name with optional time-out.</fsummary> <desc> <p>Equivalent to @@ -346,7 +678,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_receive(int fd, unsigned char* bufp, int bufsize)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_receive(int fd, unsigned char* bufp, int bufsize)</nametext></name> <fsummary>Receive a message.</fsummary> <desc> <p>Receives a message consisting of a sequence @@ -387,7 +719,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_receive_encoded(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen)</nametext></name> <fsummary>Obsolete function for receiving a message.</fsummary> <desc> <p>This function is retained for compatibility with code @@ -417,7 +749,7 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_receive_encoded_tmo(int fd, char **mbufp, int *bufsz, erlang_msg *msg, int *msglen, unsigned timeout_ms)</nametext></name> <fsummary>Obsolete function for receiving a message with time-out. </fsummary> <desc> @@ -428,8 +760,8 @@ if (ei_connect_init(&ec, "madonna", "cookie...", n++) < 0) { </func> <func> - <name><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name> - <name><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_receive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_xreceive_msg(int fd, erlang_msg* msg, ei_x_buff* x)</nametext></name> <fsummary>Receive a message.</fsummary> <desc> <p>Receives a message to the buffer in <c>x</c>. @@ -493,8 +825,8 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name> - <name><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_receive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned imeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_xreceive_msg_tmo(int fd, erlang_msg* msg, ei_x_buff* x, unsigned timeout_ms)</nametext></name> <fsummary>Receive a message with optional time-out.</fsummary> <desc> <p>Equivalent to <c>ei_receive_msg</c> and <c>ei_xreceive_msg</c> @@ -504,7 +836,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>ei_receive_tmo(int fd, unsigned char* bufp, int bufsize, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_receive_tmo(int fd, unsigned char* bufp, int bufsize, unsigned timeout_ms)</nametext></name> <fsummary>Receive a message with optional time-out.</fsummary> <desc> <p>Equivalent to @@ -514,7 +846,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_send(ei_cnode* ec, int fd, char* server_name, char* buf, int len)</nametext></name> <fsummary>Send a message to a registered name.</fsummary> <desc> <p>Sends an Erlang term to a registered process.</p> @@ -546,7 +878,7 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0) </func> <func> - <name><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_send_tmo(ei_cnode* ec, int fd, char* server_name, char* buf, int len, unsigned timeout_ms)</nametext></name> <fsummary>Send a message to a registered name with optional time-out </fsummary> <desc> @@ -557,9 +889,9 @@ if (ei_reg_send(&ec, fd, x.buff, x.index) < 0) </func> <func> - <name><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name> - <name><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name> - <name><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_rpc(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen, ei_x_buff *x)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, const char *argbuf, int argbuflen)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x)</nametext></name> <fsummary>Remote Procedure Call from C to Erlang.</fsummary> <desc> <p>Supports calling Erlang functions on remote nodes. @@ -658,7 +990,7 @@ if (ei_decode_version(result.buff, &index) < 0 </func> <func> - <name><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name> + <name since=""><ret>erlang_pid *</ret><nametext>ei_self(ei_cnode *ec)</nametext></name> <fsummary>Retrieve the pid of the C-node.</fsummary> <desc> <p>Retrieves the pid of the C-node. Every C-node @@ -671,7 +1003,7 @@ if (ei_decode_version(result.buff, &index) < 0 </func> <func> - <name><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_send(int fd, erlang_pid* to, char* buf, int len)</nametext></name> <fsummary>Send a message.</fsummary> <desc> <p>Sends an Erlang term to a process.</p> @@ -692,7 +1024,7 @@ if (ei_decode_version(result.buff, &index) < 0 </func> <func> - <name><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_send_encoded(int fd, erlang_pid* to, char* buf, int len)</nametext></name> <fsummary>Obsolete function to send a message.</fsummary> <desc> <p>Works exactly as <c>ei_send</c>, the alternative name is retained for @@ -702,7 +1034,7 @@ if (ei_decode_version(result.buff, &index) < 0 </func> <func> - <name><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_send_encoded_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name> <fsummary>Obsolete function to send a message with optional time-out. </fsummary> <desc> @@ -713,7 +1045,7 @@ if (ei_decode_version(result.buff, &index) < 0 </func> <func> - <name><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_send_reg_encoded(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name> <fsummary>Obsolete function to send a message to a registered name. </fsummary> <desc> @@ -741,7 +1073,7 @@ self->num = fd; </func> <func> - <name><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, const char *to, const char *buf, int len)</nametext></name> <fsummary>Obsolete function to send a message to a registered name with time-out.</fsummary> <desc> @@ -752,7 +1084,7 @@ self->num = fd; </func> <func> - <name><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_send_tmo(int fd, erlang_pid* to, char* buf, int len, unsigned timeout_ms)</nametext></name> <fsummary>Send a message with optional time-out.</fsummary> <desc> <p>Equivalent to @@ -762,9 +1094,9 @@ self->num = fd; </func> <func> - <name><ret>const char *</ret><nametext>ei_thisnodename(ei_cnode *ec)</nametext></name> - <name><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name> - <name><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name> + <name since=""><ret>const char *</ret><nametext>ei_thisnodename(ei_cnode *ec)</nametext></name> + <name since=""><ret>const char *</ret><nametext>ei_thishostname(ei_cnode *ec)</nametext></name> + <name since=""><ret>const char *</ret><nametext>ei_thisalivename(ei_cnode *ec)</nametext></name> <fsummary>Retrieve some values.</fsummary> <desc> <p>Can be used to retrieve information about @@ -779,7 +1111,7 @@ self->num = fd; </func> <func> - <name><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_unpublish(ei_cnode *ec)</nametext></name> <fsummary>Forcefully unpublish a node name.</fsummary> <desc> <p>Can be called by a process to unregister a @@ -802,7 +1134,7 @@ self->num = fd; </func> <func> - <name><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_unpublish_tmo(ei_cnode *ec, unsigned timeout_ms)</nametext></name> <fsummary>Unpublish a node name with optional time-out.</fsummary> <desc> <p>Equivalent to diff --git a/lib/erl_interface/doc/src/erl_connect.xml b/lib/erl_interface/doc/src/erl_connect.xml index 76ef6588c2..139ac9e2f0 100644 --- a/lib/erl_interface/doc/src/erl_connect.xml +++ b/lib/erl_interface/doc/src/erl_connect.xml @@ -49,7 +49,7 @@ <funcs> <func> - <name><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_accept(listensock, conp)</nametext></name> <fsummary>Accept a connection.</fsummary> <type> <v>int listensock;</v> @@ -78,7 +78,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_close_connection(fd)</nametext></name> <fsummary>Close a connection to an Erlang node.</fsummary> <type> <v>int fd;</v> @@ -95,8 +95,8 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>erl_connect(node)</nametext></name> - <name><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_connect(node)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_xconnect(addr, alive)</nametext></name> <fsummary>Establish a connection to an Erlang node.</fsummary> <type> <v>char *node, *alive;</v> @@ -149,8 +149,8 @@ erl_xconnect( &addr , ALIVE ); </func> <func> - <name><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name> - <name><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_connect_init(number, cookie, creation)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_connect_xinit(host, alive, node, addr, cookie, creation)</nametext></name> <fsummary>Initialize communication.</fsummary> <type> <v>int number;</v> @@ -246,7 +246,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0)) </func> <func> - <name><ret>int</ret><nametext>erl_publish(port)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_publish(port)</nametext></name> <fsummary>Publish a node name.</fsummary> <type> <v>int port;</v> @@ -277,7 +277,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0)) </func> <func> - <name><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_receive(fd, bufp, bufsize)</nametext></name> <fsummary>Receive a message.</fsummary> <type> <v>int fd;</v> @@ -316,7 +316,7 @@ if (!erl_connect_init(17, "samplecookiestring...", 0)) </func> <func> - <name><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_receive_msg(fd, bufp, bufsize, emsg)</nametext></name> <fsummary>Receive and decode a message.</fsummary> <type> <v>int fd;</v> @@ -411,7 +411,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_reg_send(fd, to, msg)</nametext></name> <fsummary>Send a message to a registered name.</fsummary> <type> <v>int fd;</v> @@ -439,9 +439,9 @@ typedef struct { </func> <func> - <name><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name> - <name><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name> - <name><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_rpc(fd, mod, fun, args)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_rpc_from(fd, timeout, emsg)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_rpc_to(fd, mod, fun, args)</nametext></name> <fsummary>Remote Procedure Call.</fsummary> <type> <v>int fd, timeout;</v> @@ -511,7 +511,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_send(fd, to, msg)</nametext></name> <fsummary>Send a message.</fsummary> <type> <v>int fd;</v> @@ -541,11 +541,11 @@ typedef struct { </func> <func> - <name><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name> - <name><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name> - <name><ret>short</ret><nametext>erl_thiscreation()</nametext></name> - <name><ret>const char *</ret><nametext>erl_thishostname()</nametext></name> - <name><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name> + <name since=""><ret>const char *</ret><nametext>erl_thisalivename()</nametext></name> + <name since=""><ret>const char *</ret><nametext>erl_thiscookie()</nametext></name> + <name since=""><ret>short</ret><nametext>erl_thiscreation()</nametext></name> + <name since=""><ret>const char *</ret><nametext>erl_thishostname()</nametext></name> + <name since=""><ret>const char *</ret><nametext>erl_thisnodename()</nametext></name> <fsummary>Retrieve some values.</fsummary> <desc> <p>Retrieves information about @@ -556,7 +556,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_unpublish(alive)</nametext></name> <fsummary>Forcefully unpublish a node name.</fsummary> <type> <v>char *alive;</v> @@ -583,7 +583,7 @@ typedef struct { </func> <func> - <name><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_xreceive_msg(fd, bufpp, bufsizep, emsg)</nametext></name> <fsummary>Receive and decode a message.</fsummary> <type> <v>int fd;</v> @@ -616,10 +616,10 @@ typedef struct { </func> <func> - <name><ret>struct hostent</ret><nametext>*erl_gethostbyaddr(addr, length, type)</nametext></name> - <name><ret>struct hostent</ret><nametext>*erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name> - <name><ret>struct hostent</ret><nametext>*erl_gethostbyname(name)</nametext></name> - <name><ret>struct hostent</ret><nametext>*erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr(addr, length, type)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyaddr_r(addr, length, type, hostp, buffer, buflen, h_errnop)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname(name)</nametext></name> + <name since=""><ret>struct hostent *</ret><nametext>erl_gethostbyname_r(name, hostp, buffer, buflen, h_errnop)</nametext></name> <fsummary>Name lookup functions.</fsummary> <type> diff --git a/lib/erl_interface/doc/src/erl_error.xml b/lib/erl_interface/doc/src/erl_error.xml index 8139c9b343..6fac94e442 100644 --- a/lib/erl_interface/doc/src/erl_error.xml +++ b/lib/erl_interface/doc/src/erl_error.xml @@ -47,7 +47,7 @@ <funcs> <func> - <name><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name> + <name since=""><ret>void</ret><nametext>erl_err_msg(FormatStr, ... )</nametext></name> <fsummary>Non-fatal error, and not system call error.</fsummary> <type> <v>const char *FormatStr;</v> @@ -59,7 +59,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name> + <name since=""><ret>void</ret><nametext>erl_err_quit(FormatStr, ... )</nametext></name> <fsummary>Fatal error, but not system call error.</fsummary> <type> <v>const char *FormatStr;</v> @@ -73,7 +73,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name> + <name since=""><ret>void</ret><nametext>erl_err_ret(FormatStr, ... )</nametext></name> <fsummary>Non-fatal system call error.</fsummary> <type> <v>const char *FormatStr;</v> @@ -86,7 +86,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name> + <name since=""><ret>void</ret><nametext>erl_err_sys(FormatStr, ... )</nametext></name> <fsummary>Fatal system call error.</fsummary> <type> <v>const char *FormatStr;</v> @@ -113,7 +113,7 @@ <funcs> <func> - <name><ret>volatile int</ret><nametext>erl_errno</nametext></name> + <name since=""><ret>volatile int</ret><nametext>erl_errno</nametext></name> <fsummary>Variable <c>erl_errno</c> contains the Erl_Interface error number. You can change the value if you wish. </fsummary> diff --git a/lib/erl_interface/doc/src/erl_eterm.xml b/lib/erl_interface/doc/src/erl_eterm.xml index 9a05196a70..070ed30dfe 100644 --- a/lib/erl_interface/doc/src/erl_eterm.xml +++ b/lib/erl_interface/doc/src/erl_eterm.xml @@ -142,7 +142,7 @@ <funcs> <func> - <name><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_cons(head, tail)</nametext></name> <fsummary>Prepend a term to the head of a list.</fsummary> <type> <v>ETERM *head;</v> @@ -181,7 +181,7 @@ erl_free_compound(list); </func> <func> - <name><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_copy_term(term)</nametext></name> <fsummary>Create a copy of an Erlang term.</fsummary> <type> <v>ETERM *term;</v> @@ -193,7 +193,7 @@ erl_free_compound(list); </func> <func> - <name><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_element(position, tuple)</nametext></name> <fsummary>Extract an element from an Erlang tuple.</fsummary> <type> <v>int position;</v> @@ -215,7 +215,7 @@ erl_free_compound(list); </func> <func> - <name><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_hd(list)</nametext></name> <fsummary>Extract the first element from a list.</fsummary> <type> <v>ETERM *list;</v> @@ -230,7 +230,7 @@ erl_free_compound(list); </func> <func> - <name><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_init(NULL, 0)</nametext></name> <fsummary>Initialization routine.</fsummary> <type> <v>void *NULL;</v> @@ -245,7 +245,7 @@ erl_free_compound(list); </func> <func> - <name><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_iolist_length(list)</nametext></name> <fsummary>Return the length of an I/O list.</fsummary> <type> <v>ETERM *list;</v> @@ -262,7 +262,7 @@ erl_free_compound(list); </func> <func> - <name><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_iolist_to_binary(term)</nametext></name> <fsummary>Convert an I/O list to a binary.</fsummary> <type> <v>ETERM *list;</v> @@ -289,7 +289,7 @@ iohead ::= Binary </func> <func> - <name><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name> + <name since=""><ret>char *</ret><nametext>erl_iolist_to_string(list)</nametext></name> <fsummary>Convert an I/O list to a <c>NULL</c>-terminated string.</fsummary> <type> <v>ETERM *list;</v> @@ -312,7 +312,7 @@ iohead ::= Binary </func> <func> - <name><ret>int</ret><nametext>erl_length(list)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_length(list)</nametext></name> <fsummary>Determine the length of a list.</fsummary> <type> <v>ETERM *list;</v> @@ -328,7 +328,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_atom(string)</nametext></name> <fsummary>Create an atom.</fsummary> <type> <v>const char *string;</v> @@ -355,7 +355,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_binary(bptr, size)</nametext></name> <fsummary>Create a binary object.</fsummary> <type> <v>char *bptr;</v> @@ -378,7 +378,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_empty_list()</nametext></name> <fsummary>Create an empty Erlang list.</fsummary> <desc> <p>Creates and returns an empty Erlang list. @@ -388,7 +388,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_estring(string, len)</nametext></name> <fsummary>Create an Erlang string.</fsummary> <type> <v>char *string;</v> @@ -408,7 +408,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_float(f)</nametext></name> <fsummary>Create an Erlang float.</fsummary> <type> <v>double f;</v> @@ -426,7 +426,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_int(n)</nametext></name> <fsummary>Create an Erlang integer.</fsummary> <type> <v>int n;</v> @@ -443,7 +443,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_list(array, arrsize)</nametext></name> <fsummary>Create a list from an array.</fsummary> <type> <v>ETERM **array;</v> @@ -465,7 +465,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_long_ref(node, n1, n2, n3, creation)</nametext></name> <fsummary>Create an Erlang reference.</fsummary> <type> <v>const char *node;</v> @@ -495,7 +495,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_pid(node, number, serial, creation)</nametext></name> <fsummary>Create a process identifier.</fsummary> <type> <v>const char *node;</v> @@ -525,7 +525,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_port(node, number, creation)</nametext></name> <fsummary>Create a port identifier.</fsummary> <type> <v>const char *node;</v> @@ -550,7 +550,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_ref(node, number, creation)</nametext></name> <fsummary>Create an old Erlang reference.</fsummary> <type> <v>const char *node;</v> @@ -578,7 +578,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_string(string)</nametext></name> <fsummary>Create a string.</fsummary> <type> <v>char *string;</v> @@ -593,7 +593,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_tuple(array, arrsize)</nametext></name> <fsummary>Create an Erlang tuple from an array.</fsummary> <type> <v>ETERM **array;</v> @@ -621,7 +621,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_uint(n)</nametext></name> <fsummary>Create an unsigned integer.</fsummary> <type> <v>unsigned int n;</v> @@ -638,7 +638,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_mk_var(name)</nametext></name> <fsummary>Create an Erlang variable.</fsummary> <type> <v>char *name;</v> @@ -653,7 +653,7 @@ iohead ::= Binary </func> <func> - <name><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_print_term(stream, term)</nametext></name> <fsummary>Print an Erlang term.</fsummary> <type> <v>FILE *stream;</v> @@ -672,7 +672,7 @@ iohead ::= Binary </func> <func> - <name><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_set_compat_rel(release_number)</nametext></name> <fsummary>Set the Erl_Interface library in compatibility mode.</fsummary> <type> <v>unsigned release_number;</v> @@ -706,7 +706,7 @@ iohead ::= Binary </func> <func> - <name><ret>int</ret><nametext>erl_size(term)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_size(term)</nametext></name> <fsummary>Return the arity of a tuple or binary.</fsummary> <type> <v>ETERM *term;</v> @@ -723,7 +723,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_tl(list)</nametext></name> <fsummary>Extract the tail from a list.</fsummary> <type> <v>ETERM *list;</v> @@ -738,7 +738,7 @@ iohead ::= Binary </func> <func> - <name><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_var_content(term, name)</nametext></name> <fsummary>Extract the content of a variable.</fsummary> <type> <v>ETERM *term;</v> diff --git a/lib/erl_interface/doc/src/erl_format.xml b/lib/erl_interface/doc/src/erl_format.xml index 5b8b7b5e78..b5e895c720 100644 --- a/lib/erl_interface/doc/src/erl_format.xml +++ b/lib/erl_interface/doc/src/erl_format.xml @@ -41,7 +41,7 @@ <funcs> <func> - <name><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_format(FormatStr, ...)</nametext></name> <fsummary>Create an Erlang term.</fsummary> <type> <v>char *FormatStr;</v> @@ -81,7 +81,7 @@ erl_format("[{name,~a},{age,~i},{data,~w}]", </func> <func> - <name><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_match(Pattern, Term)</nametext></name> <fsummary>Perform pattern matching.</fsummary> <type> <v>ETERM *Pattern,*Term;</v> diff --git a/lib/erl_interface/doc/src/erl_global.xml b/lib/erl_interface/doc/src/erl_global.xml index 2fa0045adf..72d43e81d5 100644 --- a/lib/erl_interface/doc/src/erl_global.xml +++ b/lib/erl_interface/doc/src/erl_global.xml @@ -48,7 +48,7 @@ <funcs> <func> - <name><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name> + <name since=""><ret>char **</ret><nametext>erl_global_names(fd,count)</nametext></name> <fsummary>Obtain list of global names.</fsummary> <type> <v>int fd;</v> @@ -79,7 +79,7 @@ </func> <func> - <name><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_global_register(fd,name,pid)</nametext></name> <fsummary>Register a name in global.</fsummary> <type> <v>int fd;</v> @@ -103,7 +103,7 @@ </func> <func> - <name><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_global_unregister(fd,name)</nametext></name> <fsummary>Unregister a name from global.</fsummary> <type> <v>int fd;</v> @@ -122,7 +122,7 @@ </func> <func> - <name><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_global_whereis(fd,name,node)</nametext></name> <fsummary>Look up a name in global.</fsummary> <type> <v>int fd;</v> diff --git a/lib/erl_interface/doc/src/erl_malloc.xml b/lib/erl_interface/doc/src/erl_malloc.xml index c0eebc29e9..aae3b7e078 100644 --- a/lib/erl_interface/doc/src/erl_malloc.xml +++ b/lib/erl_interface/doc/src/erl_malloc.xml @@ -41,7 +41,7 @@ <funcs> <func> - <name><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_alloc_eterm(etype)</nametext></name> <fsummary>Allocate an ETERM structure.</fsummary> <type> <v>unsigned char etype;</v> @@ -89,7 +89,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_eterm_release(void)</nametext></name> <fsummary>Clear the ETERM freelist.</fsummary> <desc> <p>Clears the freelist, where blocks are placed when they are @@ -99,7 +99,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_eterm_statistics(allocated, freed)</nametext></name> <fsummary>Report term allocation statistics.</fsummary> <type> <v>long *allocated;</v> @@ -127,7 +127,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_free(ptr)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_free(ptr)</nametext></name> <fsummary>Free some memory.</fsummary> <type> <v>void *ptr;</v> @@ -139,7 +139,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_free_array(array, size)</nametext></name> <fsummary>Free an array of ETERM structures.</fsummary> <type> <v>ETERM **array;</v> @@ -156,7 +156,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_free_compound(t)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_free_compound(t)</nametext></name> <fsummary>Free an array of ETERM structures.</fsummary> <type> <v>ETERM *t;</v> @@ -179,7 +179,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_free_term(t)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_free_term(t)</nametext></name> <fsummary>Free an ETERM structure.</fsummary> <type> <v>ETERM *t;</v> @@ -190,7 +190,7 @@ </func> <func> - <name><ret>void</ret><nametext>erl_malloc(size)</nametext></name> + <name since=""><ret>void</ret><nametext>erl_malloc(size)</nametext></name> <fsummary>Allocate some memory.</fsummary> <type> <v>long size;</v> diff --git a/lib/erl_interface/doc/src/erl_marshal.xml b/lib/erl_interface/doc/src/erl_marshal.xml index 2ad658f78b..1a6d3bb43c 100644 --- a/lib/erl_interface/doc/src/erl_marshal.xml +++ b/lib/erl_interface/doc/src/erl_marshal.xml @@ -42,7 +42,7 @@ <funcs> <func> - <name><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_compare_ext(bufp1, bufp2)</nametext></name> <fsummary>Compare encoded byte sequences.</fsummary> <type> <v>unsigned char *bufp1,*bufp2;</v> @@ -62,8 +62,8 @@ </func> <func> - <name><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name> - <name><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_decode(bufp)</nametext></name> + <name since=""><ret>ETERM *</ret><nametext>erl_decode_buf(bufpp)</nametext></name> <fsummary>Convert a term from Erlang external format.</fsummary> <type> <v>unsigned char *bufp;</v> @@ -102,8 +102,8 @@ </func> <func> - <name><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name> - <name><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_encode(term, bufp)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_encode_buf(term, bufpp)</nametext></name> <fsummary>Convert a term into Erlang external format.</fsummary> <type> <v>ETERM *term;</v> @@ -179,7 +179,7 @@ </func> <func> - <name><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_ext_size(bufp)</nametext></name> <fsummary>Count elements in encoded term.</fsummary> <type> <v>unsigned char *bufp;</v> @@ -190,7 +190,7 @@ </func> <func> - <name><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name> + <name since=""><ret>unsigned char</ret><nametext>erl_ext_type(bufp)</nametext></name> <fsummary>Determine type of an encoded byte sequence.</fsummary> <type> <v>unsigned char *bufp;</v> @@ -228,7 +228,7 @@ </func> <func> - <name><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name> + <name since=""><ret>unsigned char *</ret><nametext>erl_peek_ext(bufp, pos)</nametext></name> <fsummary>Step over encoded term.</fsummary> <type> <v>unsigned char *bufp;</v> @@ -252,7 +252,7 @@ </func> <func> - <name><ret>int</ret><nametext>erl_term_len(t)</nametext></name> + <name since=""><ret>int</ret><nametext>erl_term_len(t)</nametext></name> <fsummary>Determine encoded size of term.</fsummary> <type> <v>ETERM *t;</v> diff --git a/lib/erl_interface/doc/src/registry.xml b/lib/erl_interface/doc/src/registry.xml index 6d70fb3475..1c90c5c9dd 100644 --- a/lib/erl_interface/doc/src/registry.xml +++ b/lib/erl_interface/doc/src/registry.xml @@ -44,7 +44,7 @@ <funcs> <func> - <name><ret>int</ret><nametext>ei_reg_close(reg)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_close(reg)</nametext></name> <fsummary>Close a registry.</fsummary> <type> <v>ei_reg *reg;</v> @@ -59,7 +59,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_delete(reg,key)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_delete(reg,key)</nametext></name> <fsummary>Delete an object from the registry.</fsummary> <type> <v>ei_reg *reg;</v> @@ -85,7 +85,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_dump(fd,reg,mntab,flags)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_dump(fd,reg,mntab,flags)</nametext></name> <fsummary>Back up a registry to Mnesia.</fsummary> <type> <v>int fd;</v> @@ -125,7 +125,7 @@ </func> <func> - <name><ret>double</ret><nametext>ei_reg_getfval(reg,key)</nametext></name> + <name since=""><ret>double</ret><nametext>ei_reg_getfval(reg,key)</nametext></name> <fsummary>Get a floating point object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -151,7 +151,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_getival(reg,key)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_getival(reg,key)</nametext></name> <fsummary>Get an integer object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -177,7 +177,7 @@ </func> <func> - <name><ret>const void *</ret><nametext>ei_reg_getpval(reg,key,size)</nametext></name> + <name since=""><ret>const void *</ret><nametext>ei_reg_getpval(reg,key,size)</nametext></name> <fsummary>Get a binary object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -207,7 +207,7 @@ </func> <func> - <name><ret>const char *</ret><nametext>ei_reg_getsval(reg,key)</nametext></name> + <name since=""><ret>const char *</ret><nametext>ei_reg_getsval(reg,key)</nametext></name> <fsummary>Get a string object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -232,7 +232,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_getval(reg,key,flags,v,...)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_getval(reg,key,flags,v,...)</nametext></name> <fsummary>Get any object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -278,7 +278,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_markdirty(reg,key)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_markdirty(reg,key)</nametext></name> <fsummary>Mark an object as dirty.</fsummary> <type> <v>ei_reg *reg;</v> @@ -305,7 +305,7 @@ </func> <func> - <name><ret>ei_reg *</ret><nametext>ei_reg_open(size)</nametext></name> + <name since=""><ret>ei_reg *</ret><nametext>ei_reg_open(size)</nametext></name> <fsummary>Create and open a registry.</fsummary> <type> <v>int size;</v> @@ -326,7 +326,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_purge(reg)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_purge(reg)</nametext></name> <fsummary>Remove deleted objects.</fsummary> <type> <v>ei_reg *reg;</v> @@ -346,7 +346,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_resize(reg,newsize)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_resize(reg,newsize)</nametext></name> <fsummary>Resize a registry.</fsummary> <type> <v>ei_reg *reg;</v> @@ -363,7 +363,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_restore(fd,reg,mntab)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_restore(fd,reg,mntab)</nametext></name> <fsummary>Restore a registry from Mnesia.</fsummary> <type> <v>int fd;</v> @@ -399,7 +399,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_setfval(reg,key,f)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_setfval(reg,key,f)</nametext></name> <fsummary>Assign a floating point object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -424,7 +424,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_setival(reg,key,i)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_setival(reg,key,i)</nametext></name> <fsummary>Assign an integer object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -448,7 +448,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_setpval(reg,key,p,size)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_setpval(reg,key,p,size)</nametext></name> <fsummary>Assign a binary object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -479,7 +479,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_setsval(reg,key,s)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_setsval(reg,key,s)</nametext></name> <fsummary>Assign a string object.</fsummary> <type> <v>ei_reg *reg;</v> @@ -507,7 +507,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_setval(reg,key,flags,v,...)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_setval(reg,key,flags,v,...)</nametext></name> <fsummary>Assign a value to any object type.</fsummary> <type> <v>ei_reg *reg;</v> @@ -552,7 +552,7 @@ </func> <func> - <name><ret>int</ret><nametext>ei_reg_stat(reg,key,obuf)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_stat(reg,key,obuf)</nametext></name> <fsummary>Get object information.</fsummary> <type> <v>ei_reg *reg;</v> @@ -590,7 +590,7 @@ struct ei_reg_stat { </func> <func> - <name><ret>int</ret><nametext>ei_reg_tabstat(reg,obuf)</nametext></name> + <name since=""><ret>int</ret><nametext>ei_reg_tabstat(reg,obuf)</nametext></name> <fsummary>Get registry information.</fsummary> <type> <v>ei_reg *reg;</v> diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h index 948f89be85..92674571e2 100644 --- a/lib/erl_interface/include/ei.h +++ b/lib/erl_interface/include/ei.h @@ -35,6 +35,7 @@ #include <winsock2.h> #include <windows.h> #include <winbase.h> +typedef LONG_PTR ssize_t; /* Sigh... */ #endif #include <stdio.h> /* Need type FILE */ @@ -286,6 +287,31 @@ typedef struct { char nodename[MAXNODELEN+1]; } ErlConnect; +#define EI_SCLBK_INF_TMO (~((unsigned) 0)) + +#define EI_SCLBK_FLG_FULL_IMPL (1 << 0) + +typedef struct { + int flags; + + int (*socket)(void **ctx, void *setup_ctx); + int (*close)(void *ctx); + int (*listen)(void *ctx, void *addr, int *len, int backlog); + int (*accept)(void **ctx, void *addr, int *len, unsigned tmo); + int (*connect)(void *ctx, void *addr, int len, unsigned tmo); + int (*writev)(void *ctx, const void *iov, int iovcnt, ssize_t *len, unsigned tmo); + int (*write)(void *ctx, const char *buf, ssize_t *len, unsigned tmo); + int (*read)(void *ctx, char *buf, ssize_t *len, unsigned tmo); + + int (*handshake_packet_header_size)(void *ctx, int *sz); + int (*connect_handshake_complete)(void *ctx); + int (*accept_handshake_complete)(void *ctx); + int (*get_fd)(void *ctx, int *fd); + + /* end of version 1 */ + +} ei_socket_callbacks; + typedef struct ei_cnode_s { char thishostname[EI_MAXHOSTNAMELEN+1]; char thisnodename[MAXNODELEN+1]; @@ -295,6 +321,8 @@ typedef struct ei_cnode_s { char ei_connect_cookie[EI_MAX_COOKIE_SIZE+1]; short creation; erlang_pid self; + ei_socket_callbacks *cbs; + void *setup_context; } ei_cnode; typedef struct in_addr *Erl_IpAddr; @@ -308,7 +336,6 @@ typedef struct ei_x_buff_TAG { int index; } ei_x_buff; - /* -------------------------------------------------------------------- */ /* Function definitions (listed in same order as documentation) */ /* -------------------------------------------------------------------- */ @@ -322,6 +349,16 @@ int ei_connect_xinit (ei_cnode* ec, const char *thishostname, Erl_IpAddr thisipaddr, const char *cookie, const short creation); +int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, + const char *cookie, short creation, + ei_socket_callbacks *cbs, int cbs_sz, + void *setup_context); +int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, + const char *thisalivename, const char *thisnodename, + Erl_IpAddr thisipaddr, const char *cookie, + const short creation, ei_socket_callbacks *cbs, + int cbs_sz, void *setup_context); + int ei_connect(ei_cnode* ec, char *nodename); int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms); int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename); @@ -348,11 +385,15 @@ int ei_rpc_from(ei_cnode* ec, int fd, int timeout, erlang_msg* msg, int ei_publish(ei_cnode* ec, int port); int ei_publish_tmo(ei_cnode* ec, int port, unsigned ms); +int ei_listen(ei_cnode *ec, int *port, int backlog); +int ei_xlisten(ei_cnode *ec, Erl_IpAddr adr, int *port, int backlog); int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp); int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms); int ei_unpublish(ei_cnode* ec); int ei_unpublish_tmo(const char *alive, unsigned ms); +int ei_close_connection(int fd); + const char *ei_thisnodename(const ei_cnode* ec); const char *ei_thishostname(const ei_cnode* ec); const char *ei_thisalivename(const ei_cnode* ec); diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c index 9df4fa3b6c..1132c9fc23 100644 --- a/lib/erl_interface/src/connect/ei_connect.c +++ b/lib/erl_interface/src/connect/ei_connect.c @@ -21,8 +21,6 @@ * Purpose: Connect to any node at any host. (EI version) */ -#include "eidef.h" - #include <stdlib.h> #include <sys/types.h> #include <fcntl.h> @@ -84,7 +82,9 @@ #include <string.h> #include <errno.h> #include <ctype.h> +#include <stddef.h> +#include "eidef.h" #include "eiext.h" #include "ei_portio.h" #include "ei_internal.h" @@ -103,6 +103,10 @@ int ei_tracelevel = 0; #define COOKIE_FILE "/.erlang.cookie" #define EI_MAX_HOME_PATH 1024 +#define EI_SOCKET_CALLBACKS_SZ_V1 \ + (offsetof(ei_socket_callbacks, get_fd) \ + + sizeof(int (*)(void *))) + /* FIXME why not macro? */ static char *null_cookie = ""; @@ -113,35 +117,51 @@ static int get_home(char *buf, int size); static unsigned gen_challenge(void); static void gen_digest(unsigned challenge, char cookie[], unsigned char digest[16]); -static int send_status(int fd, char *status, unsigned ms); -static int recv_status(int fd, unsigned ms); -static int send_challenge(int fd, char *nodename, - unsigned challenge, unsigned version, unsigned ms); -static int recv_challenge(int fd, unsigned *challenge, - unsigned *version, - unsigned *flags, ErlConnect *namebuf, unsigned ms); -static int send_challenge_reply(int fd, unsigned char digest[16], +static int send_status(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, char *status, unsigned ms); +static int recv_status(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned ms); +static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + char *nodename, unsigned challenge, + unsigned version, unsigned ms); +static int recv_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + unsigned *challenge, unsigned *version, + unsigned *flags, char *namebuf, unsigned ms); +static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned char digest[16], unsigned challenge, unsigned ms); -static int recv_challenge_reply(int fd, - unsigned our_challenge, +static int recv_challenge_reply(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned our_challenge, char cookie[], unsigned *her_challenge, unsigned ms); -static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms); -static int recv_challenge_ack(int fd, - unsigned our_challenge, +static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned char digest[16], + unsigned ms); +static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned our_challenge, char cookie[], unsigned ms); -static int send_name(int fd, char *nodename, - unsigned version, unsigned ms); +static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + char *nodename, unsigned version, unsigned ms); -/* Common for both handshake types */ -static int recv_name(int fd, - unsigned *version, - unsigned *flags, ErlConnect *namebuf, unsigned ms); +static int recv_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + unsigned *version, unsigned *flags, char *namebuf, + unsigned ms); static struct hostent* dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p, int buflen, int *h_errnop); +static void abort_connection(ei_socket_callbacks *cbs, void *ctx); +static int close_connection(ei_socket_callbacks *cbs, void *ctx, int fd); + +static char * +estr(int e) +{ + char *str = strerror(e); + if (!str) + return "unknown error"; + return str; +} /*************************************************************************** @@ -154,25 +174,206 @@ dyn_gethostbyname_r(const char *name, struct hostent *hostp, char **buffer_p, typedef struct ei_socket_info_s { int socket; + ei_socket_callbacks *cbs; + void *ctx; int dist_version; ei_cnode cnode; /* A copy, not a pointer. We don't know when freed */ char cookie[EI_MAX_COOKIE_SIZE+1]; } ei_socket_info; +/*************************************************************************** + * + * XXX + * + ***************************************************************************/ + +#ifndef ETHR_HAVE___atomic_compare_exchange_n +# define ETHR_HAVE___atomic_compare_exchange_n 0 +#endif +#ifndef ETHR_HAVE___atomic_load_n +# define ETHR_HAVE___atomic_load_n 0 +#endif +#ifndef ETHR_HAVE___atomic_store_n +# define ETHR_HAVE___atomic_store_n 0 +#endif + +#if defined(_REENTRANT) \ + && (!(ETHR_HAVE___atomic_compare_exchange_n & SIZEOF_VOID_P) \ + || !(ETHR_HAVE___atomic_load_n & SIZEOF_VOID_P) \ + || !(ETHR_HAVE___atomic_store_n & SIZEOF_VOID_P)) +# undef EI_DISABLE_SEQ_SOCKET_INFO +# define EI_DISABLE_SEQ_SOCKET_INFO +#endif + +#ifdef __WIN32__ +# undef EI_DISABLE_SEQ_SOCKET_INFO +# define EI_DISABLE_SEQ_SOCKET_INFO +#endif + +#ifndef EI_DISABLE_SEQ_SOCKET_INFO + +#ifdef _REENTRANT + +#define EI_ATOMIC_CMPXCHG_ACQ_REL(VARP, XCHGP, NEW) \ + __atomic_compare_exchange_n((VARP), (XCHGP), (NEW), 0, \ + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE) +#define EI_ATOMIC_LOAD_ACQ(VARP) \ + __atomic_load_n((VARP), __ATOMIC_ACQUIRE) +#define EI_ATOMIC_STORE_REL(VARP, NEW) \ + __atomic_store_n((VARP), (NEW), __ATOMIC_RELEASE) + +#else /* ! _REENTRANT */ + +#define EI_ATOMIC_CMPXCHG_ACQ_REL(VARP, XCHGP, NEW) \ + (*(VARP) == *(XCHGP) \ + ? ((*(VARP) = (NEW)), !0) \ + : ((*(XCHGP) = *(VARP)), 0)) +#define EI_ATOMIC_LOAD_ACQ(VARP) (*(VARP)) +#define EI_ATOMIC_STORE_REL(VARP, NEW) (*(VARP) = (NEW)) + +#endif /* ! _REENTRANT */ + +#define EI_SOCKET_INFO_SEG_BITS 5 +#define EI_SOCKET_INFO_SEG_SIZE (1 << EI_SOCKET_INFO_SEG_BITS) +#define EI_SOCKET_INFO_SEG_MASK (EI_SOCKET_INFO_SEG_SIZE - 1) + +typedef struct { + int max_fds; + ei_socket_info *segments[1]; /* Larger in reality... */ +} ei_socket_info_data__; + +static ei_socket_info_data__ *socket_info_data = NULL; + +static int init_socket_info(void) +{ + int max_fds; + int i; + size_t segments_len; + ei_socket_info_data__ *info_data, *xchg; + + if (EI_ATOMIC_LOAD_ACQ(&socket_info_data) != NULL) + return !0; /* Already initialized... */ + +#if defined(HAVE_SYSCONF) && defined(_SC_OPEN_MAX) + max_fds = sysconf(_SC_OPEN_MAX); +#else + max_fds = 1024; +#endif + + if (max_fds < 0) + return 0; + + segments_len = ((max_fds-1)/EI_SOCKET_INFO_SEG_SIZE + 1); + + info_data = malloc(sizeof(ei_socket_info_data__) + + (sizeof(ei_socket_info *)*(segments_len-1))); + if (!info_data) + return 0; + + info_data->max_fds = max_fds; + for (i = 0; i < segments_len; i++) + info_data->segments[i] = NULL; + + xchg = NULL; + if (!EI_ATOMIC_CMPXCHG_ACQ_REL(&socket_info_data, &xchg, info_data)) + free(info_data); /* Already initialized... */ + + return !0; +} + +static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec, + ei_socket_callbacks *cbs, void *ctx) +{ + int six; + ei_socket_info *seg, *si; + int socket; + + if (fd < 0 || socket_info_data->max_fds <= fd) + return -1; + + socket = fd; + six = fd >> EI_SOCKET_INFO_SEG_BITS; + seg = EI_ATOMIC_LOAD_ACQ(&socket_info_data->segments[six]); + + if (!seg) { + ei_socket_info *xchg; + int i; + seg = malloc(sizeof(ei_socket_info)*EI_SOCKET_INFO_SEG_SIZE); + if (!seg) + return -1; + for (i = 0; i < EI_SOCKET_INFO_SEG_SIZE; i++) { + seg[i].socket = -1; + } + + xchg = NULL; + if (!EI_ATOMIC_CMPXCHG_ACQ_REL(&socket_info_data->segments[six], &xchg, seg)) { + free(seg); + seg = xchg; + } + } + + si = &seg[fd & EI_SOCKET_INFO_SEG_MASK]; + + if (dist_version < 0) { + socket = -1; + si->cbs = NULL; + si->ctx = NULL; + } + else { + si->dist_version = dist_version; + si->cnode = *ec; + si->cbs = cbs; + si->ctx = ctx; + strcpy(si->cookie, cookie); + } + + EI_ATOMIC_STORE_REL(&si->socket, socket); + + return 0; +} + +static ei_socket_info* get_ei_socket_info(int fd) +{ + int six, socket; + ei_socket_info *seg, *si; + + if (fd < 0 || socket_info_data->max_fds <= fd) + return NULL; + + six = fd >> EI_SOCKET_INFO_SEG_BITS; + seg = EI_ATOMIC_LOAD_ACQ(&socket_info_data->segments[six]); + + if (!seg) + return NULL; + + si = &seg[fd & EI_SOCKET_INFO_SEG_MASK]; + socket = EI_ATOMIC_LOAD_ACQ(&si->socket); + if (socket != fd) + return NULL; + return si; +} + +#else /* EI_DISABLE_SEQ_SOCKET_INFO */ + int ei_n_sockets = 0, ei_sz_sockets = 0; ei_socket_info *ei_sockets = NULL; + #ifdef _REENTRANT ei_mutex_t* ei_sockets_lock = NULL; #endif /* _REENTRANT */ +static int init_socket_info(void) +{ +#ifdef _REENTRANT + if (ei_sockets_lock == NULL) { + ei_sockets_lock = ei_mutex_create(); + } +#endif /* _REENTRANT */ + return !0; +} -/*************************************************************************** - * - * XXX - * - ***************************************************************************/ - -static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec) +static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode *ec, + ei_socket_callbacks *cbs, void *ctx) { int i; @@ -182,11 +383,13 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode * for (i = 0; i < ei_n_sockets; ++i) { if (ei_sockets[i].socket == fd) { if (dist_version == -1) { - memmove(&ei_sockets[i], &ei_sockets[i+1], + memmove(&ei_sockets[i], &ei_sockets[i+1], sizeof(ei_sockets[0])*(ei_n_sockets-i-1)); } else { ei_sockets[i].dist_version = dist_version; /* Copy the content, see ei_socket_info */ + ei_sockets[i].cbs = cbs; + ei_sockets[i].ctx = ctx; ei_sockets[i].cnode = *ec; strcpy(ei_sockets[i].cookie, cookie); } @@ -209,7 +412,9 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode * } ei_sockets[ei_n_sockets].socket = fd; ei_sockets[ei_n_sockets].dist_version = dist_version; - ei_sockets[i].cnode = *ec; + ei_sockets[ei_n_sockets].cnode = *ec; + ei_sockets[ei_n_sockets].cbs = cbs; + ei_sockets[ei_n_sockets].ctx = ctx; strcpy(ei_sockets[ei_n_sockets].cookie, cookie); ++ei_n_sockets; } @@ -219,14 +424,6 @@ static int put_ei_socket_info(int fd, int dist_version, char* cookie, ei_cnode * return 0; } -#if 0 -/* FIXME not used ?! */ -static int remove_ei_socket_info(int fd, int dist_version, char* cookie) -{ - return put_ei_socket_info(fd, -1, NULL); -} -#endif - static ei_socket_info* get_ei_socket_info(int fd) { int i; @@ -248,6 +445,13 @@ static ei_socket_info* get_ei_socket_info(int fd) return NULL; } +#endif /* EI_DISABLE_SEQ_SOCKET_INFO */ + +static int remove_ei_socket_info(int fd) +{ + return put_ei_socket_info(fd, -1, NULL, NULL, NULL, NULL); +} + ei_cnode *ei_fd_to_cnode(int fd) { ei_socket_info *sockinfo = get_ei_socket_info(fd); @@ -255,6 +459,19 @@ ei_cnode *ei_fd_to_cnode(int fd) return &sockinfo->cnode; } +int ei_get_cbs_ctx__(ei_socket_callbacks **cbs, void **ctx, int fd) +{ + ei_socket_info *sockinfo = get_ei_socket_info(fd); + if (sockinfo) { + *cbs = sockinfo->cbs; + *ctx = sockinfo->ctx; + return 0; + } + + *cbs = NULL; + *ctx = NULL; + return EBADF; +} /*************************************************************************** * Get/Set tracelevel @@ -333,21 +550,6 @@ const char *ei_getfdcookie(int fd) return r; } -/* call with cookie to set value to use on descriptor fd, -* or specify NULL to use default -*/ -/* FIXME why defined but not used? */ -#if 0 -static int ei_setfdcookie(ei_cnode* ec, int fd, char *cookie) -{ - int dist_version = ei_distversion(fd); - - if (cookie == NULL) - cookie = ec->ei_connect_cookie; - return put_ei_socket_info(fd, dist_version, cookie); -} -#endif - static int get_int32(unsigned char *s) { return ((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | (s[3] )); @@ -405,12 +607,16 @@ static int initWinSock(void) * Initailize by setting: * thishostname, thisalivename, thisnodename and thisipaddr */ -int ei_connect_xinit(ei_cnode* ec, const char *thishostname, - const char *thisalivename, const char *thisnodename, - Erl_IpAddr thisipaddr, const char *cookie, - const short creation) +int ei_connect_xinit_ussi(ei_cnode* ec, const char *thishostname, + const char *thisalivename, const char *thisnodename, + Erl_IpAddr thisipaddr, const char *cookie, + const short creation, ei_socket_callbacks *cbs, + int cbs_sz, void *setup_context) { char *dbglevel; + + if (cbs != &ei_default_socket_callbacks) + EI_SET_HAVE_PLUGIN_SOCKET_IMPL__; /* FIXME this code was enabled for 'erl'_connect_xinit(), why not here? */ #if 0 @@ -422,12 +628,16 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname, #endif #endif -#ifdef _REENTRANT - if (ei_sockets_lock == NULL) { - ei_sockets_lock = ei_mutex_create(); + if (!init_socket_info()) { + EI_TRACE_ERR0("ei_connect_xinit","can't initiate socket info"); + return ERL_ERROR; } -#endif /* _REENTRANT */ + if (cbs_sz < EI_SOCKET_CALLBACKS_SZ_V1) { + EI_TRACE_ERR0("ei_connect_xinit","invalid size of ei_socket_callbacks struct"); + return ERL_ERROR; + } + ec->creation = creation & 0x3; /* 2 bits */ if (cookie) { @@ -469,6 +679,9 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname, ec->self.serial = 0; ec->self.creation = creation & 0x3; /* 2 bits */ + ec->cbs = cbs; + ec->setup_context = setup_context; + if ((dbglevel = getenv("EI_TRACELEVEL")) != NULL || (dbglevel = getenv("ERL_DEBUG_DIST")) != NULL) ei_tracelevel = atoi(dbglevel); @@ -476,14 +689,27 @@ int ei_connect_xinit(ei_cnode* ec, const char *thishostname, return 0; } +int ei_connect_xinit(ei_cnode* ec, const char *thishostname, + const char *thisalivename, const char *thisnodename, + Erl_IpAddr thisipaddr, const char *cookie, + const short creation) +{ + return ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename, + thisipaddr, cookie, creation, + &ei_default_socket_callbacks, + sizeof(ei_default_socket_callbacks), + NULL); +} /* * Initialize by set: thishostname, thisalivename, * thisnodename and thisipaddr. At success return 0, * otherwise return -1. */ -int ei_connect_init(ei_cnode* ec, const char* this_node_name, - const char *cookie, short creation) +int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name, + const char *cookie, short creation, + ei_socket_callbacks *cbs, int cbs_sz, + void *setup_context) { char thishostname[EI_MAXHOSTNAMELEN+1]; char thisnodename[MAXNODELEN+1]; @@ -500,12 +726,7 @@ int ei_connect_init(ei_cnode* ec, const char* this_node_name, return ERL_ERROR; } #endif /* win32 */ -#ifdef _REENTRANT - if (ei_sockets_lock == NULL) { - ei_sockets_lock = ei_mutex_create(); - } -#endif /* _REENTRANT */ - + /* gethostname requires len to be max(hostname) + 1 */ if (gethostname(thishostname, EI_MAXHOSTNAMELEN+1) == -1) { #ifdef __WIN32__ @@ -561,43 +782,22 @@ int ei_connect_init(ei_cnode* ec, const char* this_node_name, sprintf(thisnodename, "%s@%s", this_node_name, hp->h_name); } } - res = ei_connect_xinit(ec, thishostname, thisalivename, thisnodename, - (struct in_addr *)*hp->h_addr_list, cookie, creation); + res = ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename, + (struct in_addr *)*hp->h_addr_list, cookie, creation, + cbs, cbs_sz, setup_context); if (buf != buffer) free(buf); return res; } - -/* connects to port at ip-address ip_addr -* and returns fd to socket -* port has to be in host byte order -*/ -static int cnct(uint16 port, struct in_addr *ip_addr, int addr_len, unsigned ms) +int ei_connect_init(ei_cnode* ec, const char* this_node_name, + const char *cookie, short creation) { - int s, res; - struct sockaddr_in iserv_addr; - - if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - erl_errno = errno; - return ERL_ERROR; - } - - memset((char*)&iserv_addr, 0, sizeof(struct sockaddr_in)); - memcpy((char*)&iserv_addr.sin_addr, (char*)ip_addr, addr_len); - iserv_addr.sin_family = AF_INET; - iserv_addr.sin_port = htons(port); - - if ((res = ei_connect_t(s, (struct sockaddr*)&iserv_addr, - sizeof(iserv_addr),ms)) < 0) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - closesocket(s); - return ERL_ERROR; - } - - return s; -} /* cnct */ - + return ei_connect_init_ussi(ec, this_node_name, cookie, creation, + &ei_default_socket_callbacks, + sizeof(ei_default_socket_callbacks), + NULL); +} /* * Same as ei_gethostbyname_r, but also handles ERANGE error @@ -758,91 +958,218 @@ int ei_connect(ei_cnode* ec, char *nodename) * the node through epmd at that host * */ -int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr adr, char *alivename, unsigned ms) +int ei_xconnect_tmo(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename, unsigned ms) { - struct in_addr *ip_addr=(struct in_addr *) adr; + ei_socket_callbacks *cbs = ec->cbs; + void *ctx; int rport = 0; /*uint16 rport = 0;*/ int sockd; - int one = 1; int dist = 0; - ErlConnect her_name; unsigned her_flags, her_version; - + unsigned our_challenge, her_challenge; + unsigned char our_digest[16]; + int err; + int pkt_sz; + struct sockaddr_in addr; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + erl_errno = EIO; /* Default error code */ EI_TRACE_CONN1("ei_xconnect","-> CONNECT attempt to connect to %s", alivename); - if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, ms)) < 0) { + if ((rport = ei_epmd_port_tmo(ip_addr,alivename,&dist, tmo)) < 0) { EI_TRACE_ERR0("ei_xconnect","-> CONNECT can't get remote port"); /* ei_epmd_port_tmo() has set erl_errno */ return ERL_NO_PORT; } - - /* we now have port number to enode, try to connect */ - if((sockd = cnct((uint16)rport, ip_addr, sizeof(struct in_addr),ms)) < 0) { - EI_TRACE_ERR0("ei_xconnect","-> CONNECT socket connect failed"); - /* cnct() has set erl_errno */ - return ERL_CONNECT_FAIL; - } - - EI_TRACE_CONN0("ei_xconnect","-> CONNECT connected to remote"); - /* FIXME why connect before checking 'dist' output from ei_epmd_port() ?! */ if (dist <= 4) { EI_TRACE_ERR0("ei_xconnect","-> CONNECT remote version not compatible"); - goto error; + return ERL_ERROR; } - else { - unsigned our_challenge, her_challenge; - unsigned char our_digest[16]; - - if (send_name(sockd, ec->thisnodename, (unsigned) dist, ms)) - goto error; - if (recv_status(sockd, ms)) - goto error; - if (recv_challenge(sockd, &her_challenge, &her_version, - &her_flags, &her_name, ms)) - goto error; - our_challenge = gen_challenge(); - gen_digest(her_challenge, ec->ei_connect_cookie, our_digest); - if (send_challenge_reply(sockd, our_digest, our_challenge, ms)) - goto error; - if (recv_challenge_ack(sockd, our_challenge, - ec->ei_connect_cookie, ms)) - goto error; - put_ei_socket_info(sockd, dist, null_cookie, ec); /* FIXME check == 0 */ + + err = ei_socket_ctx__(cbs, &ctx, ec->setup_context); + if (err) { + EI_TRACE_ERR2("ei_xconnect","-> SOCKET failed: %s (%d)", + estr(err), err); + erl_errno = err; + return ERL_CONNECT_FAIL; + } + + memset((void *) &addr, 0, sizeof(struct sockaddr_in)); + memcpy((void *) &addr.sin_addr, (void *) ip_addr, sizeof(addr.sin_addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(rport); + + err = ei_connect_ctx_t__(cbs, ctx, (void *) &addr, sizeof(addr), tmo); + if (err) { + EI_TRACE_ERR2("ei_xconnect","-> CONNECT socket connect failed: %s (%d)", + estr(err), err); + abort_connection(cbs, ctx); + erl_errno = err; + return ERL_CONNECT_FAIL; } - setsockopt(sockd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)); - setsockopt(sockd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)); + EI_TRACE_CONN0("ei_xconnect","-> CONNECT connected to remote"); - EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename); + err = EI_GET_FD__(cbs, ctx, &sockd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + goto error; + } + + err = cbs->handshake_packet_header_size(ctx, &pkt_sz); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + goto error; + } + + if (send_name(cbs, ctx, pkt_sz, ec->thisnodename, (unsigned) dist, tmo)) + goto error; + if (recv_status(cbs, ctx, pkt_sz, tmo)) + goto error; + if (recv_challenge(cbs, ctx, pkt_sz, &her_challenge, + &her_version, &her_flags, NULL, tmo)) + goto error; + our_challenge = gen_challenge(); + gen_digest(her_challenge, ec->ei_connect_cookie, our_digest); + if (send_challenge_reply(cbs, ctx, pkt_sz, our_digest, our_challenge, tmo)) + goto error; + if (recv_challenge_ack(cbs, ctx, pkt_sz, our_challenge, + ec->ei_connect_cookie, tmo)) + goto error; + if (put_ei_socket_info(sockd, dist, null_cookie, ec, cbs, ctx) != 0) + goto error; + + if (cbs->connect_handshake_complete) { + err = cbs->connect_handshake_complete(ctx); + if (err) { + EI_TRACE_ERR2("ei_xconnect","-> CONNECT failed: %s (%d)", + estr(err), err); + close_connection(cbs, ctx, sockd); + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } + } + EI_TRACE_CONN1("ei_xconnect","-> CONNECT (ok) remote = %s",alivename); + erl_errno = 0; return sockd; error: EI_TRACE_ERR0("ei_xconnect","-> CONNECT failed"); - closesocket(sockd); + abort_connection(cbs, ctx); return ERL_ERROR; } /* ei_xconnect */ -int ei_xconnect(ei_cnode* ec, Erl_IpAddr adr, char *alivename) +int ei_xconnect(ei_cnode* ec, Erl_IpAddr ip_addr, char *alivename) { - return ei_xconnect_tmo(ec, adr, alivename, 0); + return ei_xconnect_tmo(ec, ip_addr, alivename, 0); } +int ei_listen(ei_cnode *ec, int *port, int backlog) +{ + struct in_addr ip_addr; + ip_addr.s_addr = htonl(INADDR_ANY); + return ei_xlisten(ec, &ip_addr, port, backlog); +} + +int ei_xlisten(ei_cnode *ec, struct in_addr *ip_addr, int *port, int backlog) +{ + ei_socket_callbacks *cbs = ec->cbs; + struct sockaddr_in sock_addr; + void *ctx; + int fd, err, len; + + err = ei_socket_ctx__(cbs, &ctx, ec->setup_context); + if (err) { + EI_TRACE_ERR2("ei_xlisten","-> SOCKET failed: %s (%d)", + estr(err), err); + erl_errno = err; + return ERL_ERROR; + } + + memset((void *) &sock_addr, 0, sizeof(struct sockaddr_in)); + memcpy((void *) &sock_addr.sin_addr, (void *) ip_addr, sizeof(*ip_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_port = htons((short) *port); + + len = sizeof(sock_addr); + err = ei_listen_ctx__(cbs, ctx, (void *) &sock_addr, &len, backlog); + if (err) { + EI_TRACE_ERR2("ei_xlisten","-> listen failed: %s (%d)", + estr(err), err); + erl_errno = err; + goto error; + } + + if (len != sizeof(sock_addr)) { + if (len < offsetof(struct sockaddr_in, sin_addr) + sizeof(sock_addr.sin_addr) + || len < offsetof(struct sockaddr_in, sin_port) + sizeof(sock_addr.sin_port)) { + erl_errno = EIO; + EI_TRACE_ERR0("ei_xlisten","-> get info failed"); + goto error; + } + } + + memcpy((void *) ip_addr, (void *) &sock_addr.sin_addr, sizeof(*ip_addr)); + *port = (int) ntohs(sock_addr.sin_port); + + err = EI_GET_FD__(cbs, ctx, &fd); + if (err) { + erl_errno = err; + goto error; + } + + if (put_ei_socket_info(fd, 0, null_cookie, ec, cbs, ctx) != 0) { + EI_TRACE_ERR0("ei_xlisten","-> save socket info failed"); + erl_errno = EIO; + goto error; + } + + erl_errno = 0; + + return fd; + +error: + abort_connection(cbs, ctx); + return ERL_ERROR; +} + +static int close_connection(ei_socket_callbacks *cbs, void *ctx, int fd) +{ + int err; + remove_ei_socket_info(fd); + err = ei_close_ctx__(cbs, ctx); + if (err) { + erl_errno = err; + return ERL_ERROR; + } + return 0; +} - /* - * For symmetry reasons -*/ -#if 0 int ei_close_connection(int fd) { - return closesocket(fd); + ei_socket_callbacks *cbs; + void *ctx; + int err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); + if (err) + erl_errno = err; + else { + if (close_connection(cbs, ctx, fd) == 0) + return 0; + } + EI_TRACE_ERR2("ei_close_connection","<- CLOSE socket close failed: %s (%d)", + estr(erl_errno), erl_errno); + return ERL_ERROR; } /* ei_close_connection */ -#endif + +static void abort_connection(ei_socket_callbacks *cbs, void *ctx) +{ + (void) ei_close_ctx__(cbs, ctx); +} /* * Accept and initiate a connection from another @@ -857,25 +1184,71 @@ int ei_accept(ei_cnode* ec, int lfd, ErlConnect *conp) int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms) { int fd; - struct sockaddr_in cli_addr; - int cli_addr_len=sizeof(struct sockaddr_in); unsigned her_version, her_flags; - ErlConnect her_name; + char tmp_nodename[MAXNODELEN+1]; + char *her_name; + int pkt_sz, err; + struct sockaddr_in addr; + int addr_len = sizeof(struct sockaddr_in); + ei_socket_callbacks *cbs; + void *ctx; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; erl_errno = EIO; /* Default error code */ + + err = EI_GET_CBS_CTX__(&cbs, &ctx, lfd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } + EI_TRACE_CONN0("ei_accept","<- ACCEPT waiting for connection"); + + if (conp) { + her_name = &conp->nodename[0]; + } + else { + her_name = &tmp_nodename[0]; + } - if ((fd = ei_accept_t(lfd, (struct sockaddr*) &cli_addr, - &cli_addr_len, ms )) < 0) { - EI_TRACE_ERR0("ei_accept","<- ACCEPT socket accept failed"); - erl_errno = (fd == -2) ? ETIMEDOUT : EIO; - goto error; + /* + * ei_accept_ctx_t__() replaces the pointer to the listen context + * with a pointer to the accepted connection context on success. + */ + err = ei_accept_ctx_t__(cbs, &ctx, (void *) &addr, &addr_len, tmo); + if (err) { + EI_TRACE_ERR2("ei_accept","<- ACCEPT socket accept failed: %s (%d)", + estr(err), err); + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } + + err = EI_GET_FD__(cbs, ctx, &fd); + if (err) { + EI_TRACE_ERR2("ei_accept","<- ACCEPT get fd failed: %s (%d)", + estr(err), err); + EI_CONN_SAVE_ERRNO__(err); + } + + if (addr_len != sizeof(struct sockaddr_in)) { + if (addr_len < (offsetof(struct sockaddr_in, sin_addr) + + sizeof(addr.sin_addr))) { + EI_TRACE_ERR0("ei_accept","<- ACCEPT get addr failed"); + goto error; + } + } + + err = cbs->handshake_packet_header_size(ctx, &pkt_sz); + if (err) { + EI_TRACE_ERR2("ei_accept","<- ACCEPT get packet size failed: %s (%d)", + estr(err), err); + EI_CONN_SAVE_ERRNO__(err); } EI_TRACE_CONN0("ei_accept","<- ACCEPT connected to remote"); - if (recv_name(fd, &her_version, &her_flags, &her_name, ms)) { + if (recv_name(cbs, ctx, pkt_sz, &her_version, &her_flags, her_name, tmo)) { EI_TRACE_ERR0("ei_accept","<- ACCEPT initial ident failed"); goto error; } @@ -888,34 +1261,46 @@ int ei_accept_tmo(ei_cnode* ec, int lfd, ErlConnect *conp, unsigned ms) unsigned our_challenge; unsigned her_challenge; unsigned char our_digest[16]; - - if (send_status(fd,"ok", ms)) + + if (send_status(cbs, ctx, pkt_sz, "ok", tmo)) goto error; our_challenge = gen_challenge(); - if (send_challenge(fd, ec->thisnodename, - our_challenge, her_version, ms)) + if (send_challenge(cbs, ctx, pkt_sz, ec->thisnodename, + our_challenge, her_version, tmo)) goto error; - if (recv_challenge_reply(fd, our_challenge, - ec->ei_connect_cookie, - &her_challenge, ms)) + if (recv_challenge_reply(cbs, ctx, pkt_sz, our_challenge, + ec->ei_connect_cookie, &her_challenge, tmo)) goto error; gen_digest(her_challenge, ec->ei_connect_cookie, our_digest); - if (send_challenge_ack(fd, our_digest, ms)) + if (send_challenge_ack(cbs, ctx, pkt_sz, our_digest, tmo)) goto error; - put_ei_socket_info(fd, her_version, null_cookie, ec); + if (put_ei_socket_info(fd, her_version, null_cookie, ec, cbs, ctx) != 0) + goto error; + } + if (conp) { + memcpy((void *) conp->ipadr, (void *) &addr.sin_addr, sizeof(conp->ipadr)); + strcpy(&conp->nodename[0], her_name); + } + + if (cbs->accept_handshake_complete) { + err = cbs->accept_handshake_complete(ctx); + if (err) { + EI_TRACE_ERR2("ei_xconnect","-> ACCEPT handshake failed: %s (%d)", + estr(err), err); + close_connection(cbs, ctx, fd); + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } } - if (conp) - *conp = her_name; - EI_TRACE_CONN1("ei_accept","<- ACCEPT (ok) remote = %s",her_name.nodename); + EI_TRACE_CONN1("ei_accept","<- ACCEPT (ok) remote = %s",her_name); erl_errno = 0; /* No error */ return fd; error: EI_TRACE_ERR0("ei_accept","<- ACCEPT failed"); - if (fd>=0) - closesocket(fd); + abort_connection(cbs, ctx); return ERL_ERROR; } /* ei_accept */ @@ -927,36 +1312,57 @@ error: */ int ei_receive_tmo(int fd, unsigned char *bufp, int bufsize, unsigned ms) { - int len; + ssize_t len; unsigned char fourbyte[4]={0,0,0,0}; - int res; - - if ((res = ei_read_fill_t(fd, (char *) bufp, 4, ms)) != 4) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + int err; + ei_socket_callbacks *cbs; + void *ctx; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + + err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } + + len = (ssize_t) 4; + err = ei_read_fill_ctx_t__(cbs, ctx, (char *) bufp, &len, tmo); + if (!err && len != (ssize_t) 4) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); return ERL_ERROR; } /* Tick handling */ - if ((len = get_int32(bufp)) == ERL_TICK) - { - ei_write_fill_t(fd, (char *) fourbyte, 4, ms); + len = get_int32(bufp); + if (len == ERL_TICK) { + len = 4; + ei_write_fill_ctx_t__(cbs, ctx, (char *) fourbyte, &len, tmo); /* FIXME ok to ignore error or timeout? */ erl_errno = EAGAIN; return ERL_TICK; } - else if (len > bufsize) - { + + if (len > bufsize) { /* FIXME: We should drain the message. */ erl_errno = EMSGSIZE; return ERL_ERROR; } - else if ((res = ei_read_fill_t(fd, (char *) bufp, len, ms)) != len) - { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return ERL_ERROR; + else { + ssize_t need = len; + err = ei_read_fill_ctx_t__(cbs, ctx, (char *) bufp, &len, tmo); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } + if (len != need) { + erl_errno = EIO; + return ERL_ERROR; + } } - return len; + return (int) len; } @@ -1112,36 +1518,11 @@ int ei_rpc_to(ei_cnode *ec, int fd, char *mod, char *fun, int ei_rpc_from(ei_cnode *ec, int fd, int timeout, erlang_msg *msg, ei_x_buff *x) { - fd_set readmask; - struct timeval tv; - struct timeval *t = NULL; - - if (timeout >= 0) { - tv.tv_sec = timeout / 1000; - tv.tv_usec = (timeout % 1000) * 1000; - t = &tv; - } - - FD_ZERO(&readmask); - FD_SET(fd,&readmask); - - switch (select(fd+1, &readmask, NULL, NULL, t)) { - case -1: - erl_errno = EIO; - return ERL_ERROR; - - case 0: - erl_errno = ETIMEDOUT; - return ERL_TIMEOUT; - - default: - if (FD_ISSET(fd, &readmask)) { - return ei_xreceive_msg(fd, msg, x); - } else { - erl_errno = EIO; - return ERL_ERROR; - } - } + unsigned tmo = timeout < 0 ? EI_SCLBK_INF_TMO : (unsigned) timeout; + int res = ei_xreceive_msg_tmo(fd, msg, x, tmo); + if (res < 0 && erl_errno == ETIMEDOUT) + return ERL_TIMEOUT; + return res; } /* rpc_from */ /* @@ -1295,19 +1676,34 @@ static char *hex(char digest[16], char buff[33]) return buff; } -static int read_2byte_package(int fd, char **buf, int *buflen, - int *is_static, unsigned ms) +static int read_hs_package(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, char **buf, int *buflen, + int *is_static, unsigned ms) { - unsigned char nbuf[2]; + unsigned char nbuf[4]; unsigned char *x = nbuf; - unsigned len; - int res; - - if((res = ei_read_fill_t(fd, (char *)nbuf, 2, ms)) != 2) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + ssize_t len, need; + int err; + + len = (ssize_t) pkt_sz; + err = ei_read_fill_ctx_t__(cbs, ctx, (char *)nbuf, &len, ms); + if (!err && len != (ssize_t) pkt_sz) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); return -1; } - len = get16be(x); + + switch (pkt_sz) { + case 2: + len = get16be(x); + break; + case 4: + len = get32be(x); + break; + default: + return -1; + } if (len > *buflen) { if (*is_static) { @@ -1329,20 +1725,26 @@ static int read_2byte_package(int fd, char **buf, int *buflen, *buflen = len; } } - if ((res = ei_read_fill_t(fd, *buf, len, ms)) != len) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + need = len; + err = ei_read_fill_ctx_t__(cbs, ctx, *buf, &len, ms); + if (!err && len != need) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); return -1; } return len; } -static int send_status(int fd, char *status, unsigned ms) +static int send_status(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, char *status, unsigned ms) { char *buf, *s; char dbuf[DEFBUF_SIZ]; - int siz = strlen(status) + 1 + 2; - int res; + int siz = strlen(status) + 1 + pkt_sz; + int err; + ssize_t len; buf = (siz > DEFBUF_SIZ) ? malloc(siz) : dbuf; if (!buf) { @@ -1350,14 +1752,28 @@ static int send_status(int fd, char *status, unsigned ms) return -1; } s = buf; - put16be(s,siz - 2); + switch (pkt_sz) { + case 2: + put16be(s,siz - 2); + break; + case 4: + put32be(s,siz - 4); + break; + default: + return -1; + } put8(s, 's'); memcpy(s, status, strlen(status)); - if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) { - EI_TRACE_ERR0("send_status","-> SEND_STATUS socket write failed"); + len = (ssize_t) siz; + err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms); + if (!err && len != (ssize_t) siz) + err = EIO; + if (err) { + EI_TRACE_ERR2("send_status","-> SEND_STATUS socket write failed: %s (%d)", + estr(err), err); if (buf != dbuf) - free(buf); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + free(buf); + EI_CONN_SAVE_ERRNO__(err); return -1; } EI_TRACE_CONN1("send_status","-> SEND_STATUS (%s)",status); @@ -1367,7 +1783,8 @@ static int send_status(int fd, char *status, unsigned ms) return 0; } -static int recv_status(int fd, unsigned ms) +static int recv_status(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned ms) { char dbuf[DEFBUF_SIZ]; char *buf = dbuf; @@ -1375,7 +1792,8 @@ static int recv_status(int fd, unsigned ms) int buflen = DEFBUF_SIZ; int rlen; - if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) { + if ((rlen = read_hs_package(cbs, ctx, pkt_sz, + &buf, &buflen, &is_static, ms)) <= 0) { EI_TRACE_ERR1("recv_status", "<- RECV_STATUS socket read failed (%d)", rlen); goto error; @@ -1396,7 +1814,10 @@ error: return -1; } -static int send_name_or_challenge(int fd, char *nodename, +static int send_name_or_challenge(ei_socket_callbacks *cbs, + void *ctx, + int pkt_sz, + char *nodename, int f_chall, unsigned challenge, unsigned version, @@ -1405,9 +1826,10 @@ static int send_name_or_challenge(int fd, char *nodename, char *buf; unsigned char *s; char dbuf[DEFBUF_SIZ]; - int siz = 2 + 1 + 2 + 4 + strlen(nodename); + int siz = pkt_sz + 1 + 2 + 4 + strlen(nodename); const char* function[] = {"SEND_NAME", "SEND_CHALLENGE"}; - int res; + int err; + ssize_t len; if (f_chall) siz += 4; @@ -1417,7 +1839,16 @@ static int send_name_or_challenge(int fd, char *nodename, return -1; } s = (unsigned char *)buf; - put16be(s,siz - 2); + switch (pkt_sz) { + case 2: + put16be(s,siz - 2); + break; + case 4: + put32be(s,siz - 4); + break; + default: + return -1; + } put8(s, 'n'); put16be(s, version); put32be(s, (DFLAG_EXTENDED_REFERENCES @@ -1433,13 +1864,16 @@ static int send_name_or_challenge(int fd, char *nodename, if (f_chall) put32be(s, challenge); memcpy(s, nodename, strlen(nodename)); - - if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) { + len = (ssize_t) siz; + err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms); + if (!err && len != (ssize_t) siz) + err = EIO; + if (err) { EI_TRACE_ERR1("send_name_or_challenge", "-> %s socket write failed", function[f_chall]); if (buf != dbuf) free(buf); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + EI_CONN_SAVE_ERRNO__(err); return -1; } @@ -1448,9 +1882,9 @@ static int send_name_or_challenge(int fd, char *nodename, return 0; } -static int recv_challenge(int fd, unsigned *challenge, - unsigned *version, - unsigned *flags, ErlConnect *namebuf, unsigned ms) +static int recv_challenge(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned *challenge, unsigned *version, + unsigned *flags, char *namebuf, unsigned ms) { char dbuf[DEFBUF_SIZ]; char *buf = dbuf; @@ -1458,13 +1892,13 @@ static int recv_challenge(int fd, unsigned *challenge, int buflen = DEFBUF_SIZ; int rlen; char *s; - struct sockaddr_in sin; - socklen_t sin_len = sizeof(sin); char tag; - + char tmp_nodename[MAXNODELEN+1]; + erl_errno = EIO; /* Default */ - if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) { + if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, + &is_static, ms)) <= 0) { EI_TRACE_ERR1("recv_challenge", "<- RECV_CHALLENGE socket read failed (%d)",rlen); goto error; @@ -1505,22 +1939,19 @@ static int recv_challenge(int fd, unsigned *challenge, goto error; } - if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) { - EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE can't get peername"); - erl_errno = errno; - goto error; - } - memcpy(namebuf->ipadr, &(sin.sin_addr.s_addr), - sizeof(sin.sin_addr.s_addr)); - memcpy(namebuf->nodename, s, rlen - 11); - namebuf->nodename[rlen - 11] = '\0'; + if (!namebuf) + namebuf = &tmp_nodename[0]; + + memcpy(namebuf, s, rlen - 11); + namebuf[rlen - 11] = '\0'; + if (!is_static) free(buf); EI_TRACE_CONN4("recv_challenge","<- RECV_CHALLENGE (ok) node = %s, " "version = %u, " "flags = %u, " "challenge = %d", - namebuf->nodename, + namebuf, *version, *flags, *challenge @@ -1533,24 +1964,40 @@ error: return -1; } -static int send_challenge_reply(int fd, unsigned char digest[16], +static int send_challenge_reply(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned char digest[16], unsigned challenge, unsigned ms) { char *s; char buf[DEFBUF_SIZ]; - int siz = 2 + 1 + 4 + 16; - int res; + int siz = pkt_sz + 1 + 4 + 16; + int err; + ssize_t len; s = buf; - put16be(s,siz - 2); + switch (pkt_sz) { + case 2: + put16be(s,siz - 2); + break; + case 4: + put32be(s,siz - 4); + break; + default: + return -1; + } put8(s, 'r'); put32be(s, challenge); memcpy(s, digest, 16); - - if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) { - EI_TRACE_ERR0("send_challenge_reply", - "-> SEND_CHALLENGE_REPLY socket write failed"); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + + len = (ssize_t) siz; + err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms); + if (!err && len != (ssize_t) siz) + err = EIO; + if (err) { + EI_TRACE_ERR2("send_challenge_reply", + "-> SEND_CHALLENGE_REPLY socket write failed: %s (%d)", + estr(err), err); + EI_CONN_SAVE_ERRNO__(err); return -1; } @@ -1563,11 +2010,13 @@ static int send_challenge_reply(int fd, unsigned char digest[16], return 0; } -static int recv_challenge_reply (int fd, - unsigned our_challenge, - char cookie[], - unsigned *her_challenge, - unsigned ms) +static int recv_challenge_reply(ei_socket_callbacks *cbs, + void *ctx, + int pkt_sz, + unsigned our_challenge, + char cookie[], + unsigned *her_challenge, + unsigned ms) { char dbuf[DEFBUF_SIZ]; char *buf = dbuf; @@ -1580,7 +2029,7 @@ static int recv_challenge_reply (int fd, erl_errno = EIO; /* Default */ - if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) != 21) { + if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 21) { EI_TRACE_ERR1("recv_challenge_reply", "<- RECV_CHALLENGE_REPLY socket read failed (%d)",rlen); goto error; @@ -1620,23 +2069,38 @@ error: return -1; } -static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms) +static int send_challenge_ack(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + unsigned char digest[16], unsigned ms) { char *s; char buf[DEFBUF_SIZ]; - int siz = 2 + 1 + 16; - int res; + int siz = pkt_sz + 1 + 16; + int err; + ssize_t len; s = buf; - - put16be(s,siz - 2); + switch (pkt_sz) { + case 2: + put16be(s,siz - 2); + break; + case 4: + put32be(s,siz - 4); + break; + default: + return -1; + } put8(s, 'a'); memcpy(s, digest, 16); - if ((res = ei_write_fill_t(fd, buf, siz, ms)) != siz) { - EI_TRACE_ERR0("recv_challenge_reply", - "-> SEND_CHALLENGE_ACK socket write failed"); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + len = (ssize_t) siz; + err = ei_write_fill_ctx_t__(cbs, ctx, buf, &len, ms); + if (!err && len != (ssize_t) siz) + err = EIO; + if (err) { + EI_TRACE_ERR2("recv_challenge_reply", + "-> SEND_CHALLENGE_ACK socket write failed: %s (%d)", + estr(err), err); + EI_CONN_SAVE_ERRNO__(err); return -1; } @@ -1649,8 +2113,8 @@ static int send_challenge_ack(int fd, unsigned char digest[16], unsigned ms) return 0; } -static int recv_challenge_ack(int fd, - unsigned our_challenge, +static int recv_challenge_ack(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned our_challenge, char cookie[], unsigned ms) { char dbuf[DEFBUF_SIZ]; @@ -1664,7 +2128,7 @@ static int recv_challenge_ack(int fd, erl_errno = EIO; /* Default */ - if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) != 17) { + if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, &is_static, ms)) != 17) { EI_TRACE_ERR1("recv_challenge_ack", "<- RECV_CHALLENGE_ACK socket read failed (%d)",rlen); goto error; @@ -1701,20 +2165,24 @@ error: return -1; } -static int send_name(int fd, char *nodename, unsigned version, unsigned ms) +static int send_name(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + char *nodename, unsigned version, unsigned ms) { - return send_name_or_challenge(fd, nodename, 0, 0, version, ms); + return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 0, + 0, version, ms); } -static int send_challenge(int fd, char *nodename, - unsigned challenge, unsigned version, unsigned ms) +static int send_challenge(ei_socket_callbacks *cbs, void *ctx, int pkt_sz, + char *nodename, unsigned challenge, unsigned version, + unsigned ms) { - return send_name_or_challenge(fd, nodename, 1, challenge, version, ms); + return send_name_or_challenge(cbs, ctx, pkt_sz, nodename, 1, + challenge, version, ms); } -static int recv_name(int fd, - unsigned *version, - unsigned *flags, ErlConnect *namebuf, unsigned ms) +static int recv_name(ei_socket_callbacks *cbs, void *ctx, + int pkt_sz, unsigned *version, + unsigned *flags, char *namebuf, unsigned ms) { char dbuf[DEFBUF_SIZ]; char *buf = dbuf; @@ -1722,13 +2190,13 @@ static int recv_name(int fd, int buflen = DEFBUF_SIZ; int rlen; char *s; - struct sockaddr_in sin; - socklen_t sin_len = sizeof(sin); + char tmp_nodename[MAXNODELEN+1]; char tag; erl_errno = EIO; /* Default */ - if ((rlen = read_2byte_package(fd, &buf, &buflen, &is_static, ms)) <= 0) { + if ((rlen = read_hs_package(cbs, ctx, pkt_sz, &buf, &buflen, + &is_static, ms)) <= 0) { EI_TRACE_ERR1("recv_name","<- RECV_NAME socket read failed (%d)",rlen); goto error; } @@ -1759,21 +2227,18 @@ static int recv_name(int fd, erl_errno = EIO; goto error; } - - if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) { - EI_TRACE_ERR0("recv_name","<- RECV_NAME can't get peername"); - erl_errno = errno; - goto error; - } - memcpy(namebuf->ipadr, &(sin.sin_addr.s_addr), - sizeof(sin.sin_addr.s_addr)); - memcpy(namebuf->nodename, s, rlen - 7); - namebuf->nodename[rlen - 7] = '\0'; + + if (!namebuf) + namebuf = &tmp_nodename[0]; + + memcpy(namebuf, s, rlen - 7); + namebuf[rlen - 7] = '\0'; + if (!is_static) free(buf); EI_TRACE_CONN3("recv_name", "<- RECV_NAME (ok) node = %s, version = %u, flags = %u", - namebuf->nodename,*version,*flags); + namebuf,*version,*flags); erl_errno = 0; return 0; @@ -1867,3 +2332,4 @@ static int get_cookie(char *buf, int bufsize) return 1; /* Success! */ } + diff --git a/lib/erl_interface/src/connect/eirecv.c b/lib/erl_interface/src/connect/eirecv.c index 7b9dbfc387..47eea06ced 100644 --- a/lib/erl_interface/src/connect/eirecv.c +++ b/lib/erl_interface/src/connect/eirecv.c @@ -60,22 +60,36 @@ ei_recv_internal (int fd, int arity; int version; int index = 0; - int i = 0; - int res; + int err; int show_this_msg = 0; + ei_socket_callbacks *cbs; + void *ctx; + ssize_t rlen; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + + err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; + } /* get length field */ - if ((res = ei_read_fill_t(fd, header, 4, ms)) != 4) - { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; + rlen = 4; + err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo); + if (!err && rlen != 4) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); return -1; } + len = get32be(s); /* got tick - respond and return */ if (!len) { char tock[] = {0,0,0,0}; - ei_write_fill_t(fd, tock, sizeof(tock), ms); /* Failure no problem */ + ssize_t wlen = sizeof(tock); + ei_write_fill_ctx_t__(cbs, ctx, tock, &wlen, tmo); /* Failure no problem */ *msglenp = 0; return 0; /* maybe flag ERL_EAGAIN [sverkerw] */ } @@ -86,9 +100,12 @@ ei_recv_internal (int fd, ei_trace(-1,NULL); /* read enough to get at least entire header */ - bytesread = (len > EIRECVBUF ? EIRECVBUF : len); - if ((i = ei_read_fill_t(fd,header,bytesread,ms)) != bytesread) { - erl_errno = (i == -2) ? ETIMEDOUT : EIO; + rlen = bytesread = (len > EIRECVBUF ? EIRECVBUF : len); + err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo); + if (!err && rlen != bytesread) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); return -1; } @@ -212,12 +229,17 @@ ei_recv_internal (int fd, */ if (msglen > *bufsz) { if (staticbufp) { - int sz = EIRECVBUF; /* flush in rest of packet */ while (remain > 0) { - if (remain < sz) sz = remain; - if ((i=ei_read_fill_t(fd,header,sz,ms)) <= 0) break; - remain -= i; + rlen = remain > EIRECVBUF ? EIRECVBUF : remain; + err = ei_read_fill_ctx_t__(cbs, ctx, header, &rlen, tmo); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; + } + if (rlen == 0) + break; + remain -= rlen; } erl_errno = EMSGSIZE; return -1; @@ -247,11 +269,15 @@ ei_recv_internal (int fd, /* read the rest of the message into callers buffer */ if (remain > 0) { - if ((i = ei_read_fill_t(fd,mbuf+bytesread-index,remain,ms)) != remain) { - *msglenp = bytesread-index+1; /* actual bytes in users buffer */ - erl_errno = (i == -2) ? ETIMEDOUT : EIO; - return -1; - } + rlen = remain; + err = ei_read_fill_ctx_t__(cbs, ctx, mbuf+bytesread-index, &rlen, tmo); + if (!err && rlen != remain) + err = EIO; + if (err) { + *msglenp = bytesread-index+1; /* actual bytes in users buffer */ + EI_CONN_SAVE_ERRNO__(err); + return -1; + } } if (show_this_msg) diff --git a/lib/erl_interface/src/connect/send.c b/lib/erl_interface/src/connect/send.c index 37d7db6d68..d97532d123 100644 --- a/lib/erl_interface/src/connect/send.c +++ b/lib/erl_interface/src/connect/send.c @@ -58,10 +58,17 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to, char *s, header[1200]; /* see size calculation below */ erlang_trace *token = NULL; int index = 5; /* reserve 5 bytes for control message */ - int res; -#ifdef HAVE_WRITEV - struct iovec v[2]; -#endif + int err; + ei_socket_callbacks *cbs; + void *ctx; + ssize_t len, tot_len; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + + err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } /* are we tracing? */ /* check that he can receive trace tokens first */ @@ -91,30 +98,47 @@ int ei_send_encoded_tmo(int fd, const erlang_pid *to, if (ei_tracelevel >= 4) ei_show_sendmsg(stderr,header,msg); -#ifdef HAVE_WRITEV - - v[0].iov_base = (char *)header; - v[0].iov_len = index; - v[1].iov_base = (char *)msg; - v[1].iov_len = msglen; - - if ((res = ei_writev_fill_t(fd,v,2,ms)) != index+msglen) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; - } - -#else /* !HAVE_WRITEV */ - - if ((res = ei_write_fill_t(fd,header,index,ms)) != index) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + +#ifdef EI_HAVE_STRUCT_IOVEC__ + if (ei_socket_callbacks_have_writev__(cbs)) { + struct iovec v[2]; + + v[0].iov_base = (char *)header; + v[0].iov_len = index; + v[1].iov_base = (char *)msg; + v[1].iov_len = msglen; + + len = tot_len = (ssize_t) index+msglen; + err = ei_writev_fill_ctx_t__(cbs, ctx, v, 2, &len, tmo); + if (!err && len != tot_len) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; + } + + return 0; } - if ((res = ei_write_fill_t(fd,msg,msglen,ms)) != msglen) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; +#endif /* EI_HAVE_STRUCT_IOVEC__ */ + + /* no writev() */ + len = tot_len = (ssize_t) index; + err = ei_write_fill_ctx_t__(cbs, ctx, header, &len, tmo); + if (!err && len != tot_len) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; } -#endif /* !HAVE_WRITEV */ + len = tot_len = (ssize_t) msglen; + err = ei_write_fill_ctx_t__(cbs, ctx, msg, &len, tmo); + if (!err && len != tot_len) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; + } return 0; } diff --git a/lib/erl_interface/src/connect/send_exit.c b/lib/erl_interface/src/connect/send_exit.c index 2e298e3221..b4f7e14c7f 100644 --- a/lib/erl_interface/src/connect/send_exit.c +++ b/lib/erl_interface/src/connect/send_exit.c @@ -55,6 +55,17 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to, char *s; int index = 0; int len = strlen(reason) + 1080; /* see below */ + ei_socket_callbacks *cbs; + void *ctx; + int err; + ssize_t wlen; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + + err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } if (len > EISMALLBUF) if (!(dbuf = malloc(len))) @@ -92,10 +103,16 @@ int ei_send_exit_tmo(int fd, const erlang_pid *from, const erlang_pid *to, if (ei_tracelevel >= 4) ei_show_sendmsg(stderr,msgbuf,NULL); - ei_write_fill_t(fd,msgbuf,index,ms); - /* FIXME ignore timeout etc? erl_errno?! */ - - if (dbuf) free(dbuf); + wlen = (ssize_t) index; + err = ei_write_fill_ctx_t__(cbs, ctx, msgbuf, &wlen, tmo); + if (!err && wlen != (ssize_t) index) + err = EIO; + if (dbuf) + free(dbuf); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } return 0; } diff --git a/lib/erl_interface/src/connect/send_reg.c b/lib/erl_interface/src/connect/send_reg.c index 62478f042d..80d61e57b5 100644 --- a/lib/erl_interface/src/connect/send_reg.c +++ b/lib/erl_interface/src/connect/send_reg.c @@ -51,11 +51,17 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, char *s, header[1400]; /* see size calculation below */ erlang_trace *token = NULL; int index = 5; /* reserve 5 bytes for control message */ - int res; + int err; + ei_socket_callbacks *cbs; + void *ctx; + ssize_t len, tot_len; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; -#ifdef HAVE_WRITEV - struct iovec v[2]; -#endif + err = EI_GET_CBS_CTX__(&cbs, &ctx, fd); + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return ERL_ERROR; + } /* are we tracing? */ /* check that he can receive trace tokens first */ @@ -86,29 +92,45 @@ int ei_send_reg_encoded_tmo(int fd, const erlang_pid *from, if (ei_tracelevel >= 4) ei_show_sendmsg(stderr,header,msg); -#ifdef HAVE_WRITEV +#ifdef EI_HAVE_STRUCT_IOVEC__ + if (ei_socket_callbacks_have_writev__(cbs)) { + struct iovec v[2]; - v[0].iov_base = (char *)header; - v[0].iov_len = index; - v[1].iov_base = (char *)msg; - v[1].iov_len = msglen; + v[0].iov_base = (char *)header; + v[0].iov_len = index; + v[1].iov_base = (char *)msg; + v[1].iov_len = msglen; - if ((res = ei_writev_fill_t(fd,v,2,ms)) != index+msglen) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + len = tot_len = (ssize_t) index+msglen; + err = ei_writev_fill_ctx_t__(cbs, ctx, v, 2, &len, tmo); + if (!err && len != tot_len) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; + } + return 0; } -#else - +#endif /* EI_HAVE_STRUCT_IOVEC__ */ + /* no writev() */ - if ((res = ei_write_fill_t(fd,header,index,ms)) != index) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + len = tot_len = (ssize_t) index; + err = ei_write_fill_ctx_t__(cbs, ctx, header, &len, tmo); + if (!err && len != tot_len) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; } - if ((res = ei_write_fill_t(fd,msg,msglen,ms)) != msglen) { - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + + len = tot_len = (ssize_t) msglen; + err = ei_write_fill_ctx_t__(cbs, ctx, msg, &len, tmo); + if (!err && len != tot_len) + err = EIO; + if (err) { + EI_CONN_SAVE_ERRNO__(err); + return -1; } -#endif return 0; } diff --git a/lib/erl_interface/src/epmd/epmd_port.c b/lib/erl_interface/src/epmd/epmd_port.c index 2ec418b24a..492c3fb3aa 100644 --- a/lib/erl_interface/src/epmd/epmd_port.c +++ b/lib/erl_interface/src/epmd/epmd_port.c @@ -62,31 +62,38 @@ int ei_epmd_connect_tmo(struct in_addr *inaddr, unsigned ms) { static unsigned int epmd_port = 0; - struct sockaddr_in saddr; - int sd; - int res; + int port, sd, err; + struct in_addr ip_addr; + struct sockaddr_in addr; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + + err = ei_socket__(&sd); + if (err) { + erl_errno = err; + return -1; + } if (epmd_port == 0) { char* port_str = getenv("ERL_EPMD_PORT"); epmd_port = (port_str != NULL) ? atoi(port_str) : EPMD_PORT; } - memset(&saddr, 0, sizeof(saddr)); - saddr.sin_port = htons(epmd_port); - saddr.sin_family = AF_INET; - if (!inaddr) saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); - else memmove(&saddr.sin_addr,inaddr,sizeof(saddr.sin_addr)); + port = (int) epmd_port; - if (((sd = socket(PF_INET, SOCK_STREAM, 0)) < 0)) - { - erl_errno = errno; - return -1; + if (!inaddr) { + ip_addr.s_addr = htonl(INADDR_LOOPBACK); + inaddr = &ip_addr; } + + memset((void *) &addr, 0, sizeof(struct sockaddr_in)); + memcpy((void *) &addr.sin_addr, (void *) inaddr, sizeof(addr.sin_addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); - if ((res = ei_connect_t(sd,(struct sockaddr *)&saddr,sizeof(saddr),ms)) < 0) - { - erl_errno = (res == -2) ? ETIMEDOUT : errno; - closesocket(sd); + err = ei_connect_t__(sd, (void *) &addr, sizeof(addr), tmo); + if (err) { + erl_errno = err; + ei_close__(sd); return -1; } @@ -104,6 +111,9 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive, int port; int dist_high, dist_low, proto; int res; + int err; + ssize_t dlen; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; #if defined(VXWORKS) char ntoabuf[32]; #endif @@ -124,10 +134,14 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive, return -1; } - if ((res = ei_write_fill_t(fd, buf, len+2, ms)) != len+2) { - closesocket(fd); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + dlen = len + 2; + err = ei_write_fill_t__(fd, buf, &dlen, tmo); + if (!err && dlen != (ssize_t) len + 2) + erl_errno = EIO; + if (err) { + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); + return -1; } #ifdef VXWORKS @@ -142,12 +156,15 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive, "-> PORT2_REQ alive=%s ip=%s",alive,inet_ntoa(*addr)); #endif - /* read first two bytes (response type, response) */ - if ((res = ei_read_fill_t(fd, buf, 2, ms)) != 2) { - EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE"); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - closesocket(fd); - return -2; /* version mismatch */ + dlen = (ssize_t) 2; + err = ei_read_fill_t__(fd, buf, &dlen, tmo); + if (!err && dlen != (ssize_t) 2) + erl_errno = EIO; + if (err) { + EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE"); + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); + return -2; } s = buf; @@ -156,7 +173,7 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive, if (res != EI_EPMD_PORT2_RESP) { /* response type */ EI_TRACE_ERR1("ei_epmd_r4_port","<- unknown (%d)",res); EI_TRACE_ERR0("ei_epmd_r4_port","-> CLOSE"); - closesocket(fd); + ei_close__(fd); erl_errno = EIO; return -1; } @@ -167,7 +184,7 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive, if ((res = get8(s))) { /* got negative response */ EI_TRACE_ERR1("ei_epmd_r4_port","<- PORT2_RESP result=%d (failure)",res); - closesocket(fd); + ei_close__(fd); erl_errno = EIO; return -1; } @@ -175,14 +192,18 @@ static int ei_epmd_r4_port (struct in_addr *addr, const char *alive, EI_TRACE_CONN1("ei_epmd_r4_port","<- PORT2_RESP result=%d (ok)",res); /* expecting remaining 8 bytes */ - if ((res = ei_read_fill_t(fd,buf,8,ms)) != 8) { + dlen = (ssize_t) 8; + err = ei_read_fill_t__(fd, buf, &dlen, tmo); + if (!err && dlen != (ssize_t) 8) + err = EIO; + if (err) { EI_TRACE_ERR0("ei_epmd_r4_port","<- CLOSE"); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - closesocket(fd); + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); return -1; } - closesocket(fd); + ei_close__(fd); s = buf; port = get16be(s); diff --git a/lib/erl_interface/src/epmd/epmd_publish.c b/lib/erl_interface/src/epmd/epmd_publish.c index 47d68a6db0..20b8e867e8 100644 --- a/lib/erl_interface/src/epmd/epmd_publish.c +++ b/lib/erl_interface/src/epmd/epmd_publish.c @@ -68,8 +68,10 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms) int nlen = strlen(alive); int len = elen + nlen + 13; /* hard coded: be careful! */ int n; - int res, creation; - + int err, res, creation; + ssize_t dlen; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; + if (len > sizeof(buf)-2) { erl_errno = ERANGE; @@ -93,29 +95,39 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms) if ((fd = ei_epmd_connect_tmo(NULL,ms)) < 0) return fd; - if ((res = ei_write_fill_t(fd, buf, len+2, ms)) != len+2) { - closesocket(fd); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + dlen = (ssize_t) len+2; + err = ei_write_fill_t__(fd, buf, &dlen, tmo); + if (!err && dlen != (ssize_t) len + 2) + erl_errno = EIO; + if (err) { + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); + return -1; } EI_TRACE_CONN6("ei_epmd_r4_publish", "-> ALIVE2_REQ alive=%s port=%d ntype=%d " "proto=%d dist-high=%d dist-low=%d", alive,port,'H',EI_MYPROTO,EI_DIST_HIGH,EI_DIST_LOW); - - if ((n = ei_read_fill_t(fd, buf, 4, ms)) != 4) { + + dlen = (ssize_t) 4; + err = ei_read_fill_t__(fd, buf, &dlen, tmo); + n = (int) dlen; + if (!err && n != 4) + err = EIO; + if (err) { EI_TRACE_ERR0("ei_epmd_r4_publish","<- CLOSE"); - closesocket(fd); - erl_errno = (n == -2) ? ETIMEDOUT : EIO; + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); return -2; /* version mismatch */ } + /* Don't close fd here! It keeps us registered with epmd */ s = buf; if (((res=get8(s)) != EI_EPMD_ALIVE2_RESP)) { /* response */ EI_TRACE_ERR1("ei_epmd_r4_publish","<- unknown (%d)",res); EI_TRACE_ERR0("ei_epmd_r4_publish","-> CLOSE"); - closesocket(fd); + ei_close__(fd); erl_errno = EIO; return -1; } @@ -124,7 +136,7 @@ static int ei_epmd_r4_publish (int port, const char *alive, unsigned ms) if (((res=get8(s)) != 0)) { /* 0 == success */ EI_TRACE_ERR1("ei_epmd_r4_publish"," result=%d (fail)",res); - closesocket(fd); + ei_close__(fd); erl_errno = EIO; return -1; } diff --git a/lib/erl_interface/src/epmd/epmd_unpublish.c b/lib/erl_interface/src/epmd/epmd_unpublish.c index 255d0ffb59..c112f74147 100644 --- a/lib/erl_interface/src/epmd/epmd_unpublish.c +++ b/lib/erl_interface/src/epmd/epmd_unpublish.c @@ -58,7 +58,9 @@ int ei_unpublish_tmo(const char *alive, unsigned ms) char buf[EPMDBUF]; char *s = (char*)buf; int len = 1 + strlen(alive); - int fd, res; + int fd, err; + ssize_t dlen; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; if (len > sizeof(buf)-3) { erl_errno = ERANGE; @@ -72,20 +74,29 @@ int ei_unpublish_tmo(const char *alive, unsigned ms) /* FIXME can't connect, return success?! At least commen whats up */ if ((fd = ei_epmd_connect_tmo(NULL,ms)) < 0) return fd; - if ((res = ei_write_fill_t(fd, buf, len+2,ms)) != len+2) { - closesocket(fd); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + dlen = (ssize_t) len+2; + err = ei_write_fill_t__(fd, buf, &dlen, tmo); + if (!err && dlen != (ssize_t) len + 2) + erl_errno = EIO; + if (err) { + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); + return -1; } EI_TRACE_CONN1("ei_unpublish_tmo","-> STOP %s",alive); - - if ((res = ei_read_fill_t(fd, buf, 7, ms)) != 7) { - closesocket(fd); - erl_errno = (res == -2) ? ETIMEDOUT : EIO; - return -1; + + dlen = (ssize_t) 7; + err = ei_read_fill_t__(fd, buf, &dlen, tmo); + if (!err && dlen != (ssize_t) 7) + erl_errno = EIO; + if (err) { + ei_close__(fd); + EI_CONN_SAVE_ERRNO__(err); + return -1; } - closesocket(fd); + + ei_close__(fd); buf[7]=(char)0; /* terminate the string */ if (!strcmp("STOPPED",(char *)buf)) { diff --git a/lib/erl_interface/src/legacy/erl_connect.c b/lib/erl_interface/src/legacy/erl_connect.c index 7ffd545d3e..e2fd4611c0 100644 --- a/lib/erl_interface/src/legacy/erl_connect.c +++ b/lib/erl_interface/src/legacy/erl_connect.c @@ -179,15 +179,13 @@ int erl_xconnect(Erl_IpAddr addr, char *alivename) * * API: erl_close_connection() * - * Close a connection. FIXME call ei_close_connection() later. - * * Returns 0 on success and -1 on failure. * ***************************************************************************/ int erl_close_connection(int fd) { - return closesocket(fd); + return ei_close_connection(fd); } /* @@ -220,7 +218,10 @@ int erl_reg_send(int fd, char *server_name, ETERM *msg) ei_x_buff x; int r; - ei_x_new_with_version(&x); + if (ei_x_new_with_version(&x) < 0) { + erl_errno = ENOMEM; + return 0; + } if (ei_x_encode_term(&x, msg) < 0) { erl_errno = EINVAL; r = 0; diff --git a/lib/erl_interface/src/misc/ei_internal.h b/lib/erl_interface/src/misc/ei_internal.h index aa6aacd703..0c58245c0a 100644 --- a/lib/erl_interface/src/misc/ei_internal.h +++ b/lib/erl_interface/src/misc/ei_internal.h @@ -22,19 +22,20 @@ #ifndef _EI_INTERNAL_H #define _EI_INTERNAL_H +#ifdef EI_HIDE_REAL_ERRNO +# define EI_CONN_SAVE_ERRNO__(E) \ + ((E) == ETIMEDOUT ? (erl_errno = ETIMEDOUT) : (erl_errno = EIO)) +#else +# define EI_CONN_SAVE_ERRNO__(E) \ + (erl_errno = (E)) +#endif + /* * Some useful stuff not to be exported to users. */ #ifdef __WIN32__ #define MAXPATHLEN 256 -#define writesocket(sock,buf,nbyte) send(sock,buf,nbyte,0) -#define readsocket(sock,buf,nbyte) recv(sock,buf,nbyte,0) -#else /* not __WIN32__ */ -#define writesocket write -#define readsocket read -#define closesocket close -#define ioctlsocket ioctl #endif /* @@ -155,4 +156,7 @@ extern int ei_tracelevel; void ei_trace_printf(const char *name, int level, const char *format, ...); int ei_internal_use_r9_pids_ports(void); + +int ei_get_cbs_ctx__(ei_socket_callbacks **cbs, void **ctx, int fd); + #endif /* _EI_INTERNAL_H */ diff --git a/lib/erl_interface/src/misc/ei_portio.c b/lib/erl_interface/src/misc/ei_portio.c index 8cd35bf2e5..726b1af82d 100644 --- a/lib/erl_interface/src/misc/ei_portio.c +++ b/lib/erl_interface/src/misc/ei_portio.c @@ -22,6 +22,7 @@ #ifdef __WIN32__ #include <winsock2.h> #include <windows.h> +#include <winbase.h> #include <process.h> #include <stdio.h> #include <stdlib.h> @@ -35,10 +36,6 @@ static unsigned long param_one = 1; #define SET_BLOCKING(Sock) ioctlsocket((Sock),FIONBIO,¶m_zero) #define SET_NONBLOCKING(Sock) ioctlsocket((Sock),FIONBIO,¶m_one) -#define ERROR_WOULDBLOCK WSAEWOULDBLOCK -#define ERROR_TIMEDOUT WSAETIMEDOUT -#define ERROR_INPROGRESS WSAEINPROGRESS -#define GET_SOCKET_ERROR() WSAGetLastError() #define MEANS_SOCKET_ERROR(Ret) ((Ret == SOCKET_ERROR)) #define IS_INVALID_SOCKET(Sock) ((Sock) == INVALID_SOCKET) @@ -53,15 +50,16 @@ static unsigned long param_one = 1; #include <sys/types.h> #include <ioLib.h> #include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <timers.h> static unsigned long param_zero = 0; static unsigned long param_one = 1; #define SET_BLOCKING(Sock) ioctl((Sock),FIONBIO,(int)¶m_zero) #define SET_NONBLOCKING(Sock) ioctl((Sock),FIONBIO,(int)¶m_one) -#define ERROR_WOULDBLOCK EWOULDBLOCK -#define ERROR_TIMEDOUT ETIMEDOUT -#define ERROR_INPROGRESS EINPROGRESS -#define GET_SOCKET_ERROR() (errno) #define MEANS_SOCKET_ERROR(Ret) ((Ret) == ERROR) #define IS_INVALID_SOCKET(Sock) ((Sock) < 0) @@ -69,106 +67,394 @@ static unsigned long param_one = 1; #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> -#include <sys/uio.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <netdb.h> -#ifndef EWOULDBLOCK -#define ERROR_WOULDBLOCK EAGAIN -#else -#define ERROR_WOULDBLOCK EWOULDBLOCK -#endif #define SET_BLOCKING(fd) fcntl((fd), F_SETFL, \ fcntl((fd), F_GETFL, 0) & ~O_NONBLOCK) #define SET_NONBLOCKING(fd) fcntl((fd), F_SETFL, \ fcntl((fd), F_GETFL, 0) | O_NONBLOCK) -#define ERROR_TIMEDOUT ETIMEDOUT -#define ERROR_INPROGRESS EINPROGRESS -#define GET_SOCKET_ERROR() (errno) #define MEANS_SOCKET_ERROR(Ret) ((Ret) < 0) #define IS_INVALID_SOCKET(Sock) ((Sock) < 0) #endif /* common includes */ -#include "eidef.h" #include <stdio.h> #include <stdlib.h> #include <string.h> -#include "ei_portio.h" -#include "ei_internal.h" - #ifdef HAVE_SYS_TIME_H #include <sys/time.h> #else #include <time.h> #endif +#include "eidef.h" +#include "ei_portio.h" +#include "ei_internal.h" + +#ifdef __WIN32__ + +#define writesocket(sock,buf,nbyte) send(sock,buf,nbyte,0) +#define readsocket(sock,buf,nbyte) recv(sock,buf,nbyte,0) -#ifdef HAVE_WRITEV -static int ei_writev_t(int fd, struct iovec *iov, int iovcnt, unsigned ms) +static int get_error(void) { - int res; - if (ms != 0) { - fd_set writemask; - struct timeval tv; - tv.tv_sec = (time_t) (ms / 1000U); - ms %= 1000U; - tv.tv_usec = (time_t) (ms * 1000U); - FD_ZERO(&writemask); - FD_SET(fd,&writemask); - switch (select(fd+1, NULL, &writemask, NULL, &tv)) { - case -1 : - return -1; /* i/o error */ - case 0: - return -2; /* timeout */ - default: - if (!FD_ISSET(fd, &writemask)) { - return -1; /* Other error */ - } - } + switch (WSAGetLastError()) { + case WSAEWOULDBLOCK: return EWOULDBLOCK; + case WSAETIMEDOUT: return ETIMEDOUT; + case WSAEINPROGRESS: return EINPROGRESS; + case WSA_NOT_ENOUGH_MEMORY: return ENOMEM; + case WSA_INVALID_PARAMETER: return EINVAL; + case WSAEBADF: return EBADF; + case WSAEINVAL: return EINVAL; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAENETUNREACH: return ENETUNREACH; + case WSAECONNABORTED: return ECONNABORTED; + case WSAECONNRESET: return ECONNRESET; + case WSAECONNREFUSED: return ECONNREFUSED; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case WSAEMFILE: return EMFILE; + case WSAEALREADY: return EALREADY; + default: return EIO; } +} + +#else /* not __WIN32__ */ + +#define writesocket write +#define readsocket read +#define closesocket close +#define ioctlsocket ioctl + +static int get_error(void) +{ + int err = errno; + if (err == 0) + return EIO; /* Make sure never to return 0 as error code... */ + return err; +} + +#endif + +int ei_plugin_socket_impl__ = 0; + +/* + * Callbacks for communication over TCP/IPv4 + */ + +static int tcp_get_fd(void *ctx, int *fd) +{ + return EI_DFLT_CTX_TO_FD__(ctx, fd); +} + +static int tcp_hs_packet_header_size(void *ctx, int *sz) +{ + int fd; + *sz = 2; + return EI_DFLT_CTX_TO_FD__(ctx, &fd); +} + +static int tcp_handshake_complete(void *ctx) +{ + int res, fd, one = 1; + + res = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (res) + return res; + + res = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one)); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + res = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (char *)&one, sizeof(one)); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + return 0; +} + +static int tcp_socket(void **ctx, void *setup_ctx) +{ + int fd = socket(AF_INET, SOCK_STREAM, 0); + if (MEANS_SOCKET_ERROR(fd)) + return get_error(); + + *ctx = EI_FD_AS_CTX__(fd); + return 0; +} + +static int tcp_close(void *ctx) +{ + int fd, res; + + res = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (res) + return res; + + res = closesocket(fd); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + return 0; +} + +static int tcp_listen(void *ctx, void *addr, int *len, int backlog) +{ + int res, fd; + socklen_t sz = (socklen_t) *len; + int on = 1; + + res = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (res) + return res; + + res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + res = bind(fd, (struct sockaddr *) addr, sz); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + res = getsockname(fd, (struct sockaddr *) addr, (socklen_t *) &sz); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + *len = (int) sz; + + res = listen(fd, backlog); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + return 0; +} + +static int tcp_accept(void **ctx, void *addr, int *len, unsigned unused) +{ + int fd, res; + socklen_t addr_len = (socklen_t) *len; + + if (!ctx) + return EINVAL; + + res = EI_DFLT_CTX_TO_FD__(*ctx, &fd); + if (res) + return res; + + res = accept(fd, (struct sockaddr*) &addr, &addr_len); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + *len = (int) addr_len; + + *ctx = EI_FD_AS_CTX__(res); + return 0; +} + +static int tcp_connect(void *ctx, void *addr, int len, unsigned unused) +{ + int res, fd; + + res = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (res) + return res; + + res = connect(fd, (struct sockaddr *) addr, len); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + + return 0; +} + +#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV) + +static int tcp_writev(void *ctx, const void *viov, int iovcnt, ssize_t *len, unsigned unused) +{ + const struct iovec *iov = (const struct iovec *) viov; + int fd, error; + ssize_t res; + + error = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (error) + return error; + res = writev(fd, iov, iovcnt); - return (res < 0) ? -1 : res; + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + *len = res; + return 0; +} + +#endif + +static int tcp_write(void *ctx, const char* buf, ssize_t *len, unsigned unused) +{ + int error, fd; + ssize_t res; + + error = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (error) + return error; + + res = writesocket(fd, buf, *len); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + *len = res; + return 0; } -int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned ms) +static int tcp_read(void *ctx, char* buf, ssize_t *len, unsigned unused) { - int i; - int done; + int error, fd; + ssize_t res; + + error = EI_DFLT_CTX_TO_FD__(ctx, &fd); + if (error) + return error; + + res = readsocket(fd, buf, *len); + if (MEANS_SOCKET_ERROR(res)) + return get_error(); + *len = res; + return 0; +} + +ei_socket_callbacks ei_default_socket_callbacks = { + 0, /* flags */ + tcp_socket, + tcp_close, + tcp_listen, + tcp_accept, + tcp_connect, +#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV) + tcp_writev, +#else + NULL, +#endif + tcp_write, + tcp_read, + + tcp_hs_packet_header_size, + tcp_handshake_complete, + tcp_handshake_complete, + tcp_get_fd + +}; + + +/* + * + */ + +#if defined(EI_HAVE_STRUCT_IOVEC__) + +int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs) +{ + return !!cbs->writev; +} + +static int writev_ctx_t__(ei_socket_callbacks *cbs, void *ctx, + const struct iovec *iov, int iovcnt, + ssize_t *len, + unsigned ms) +{ + int error; + + if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) { + int fd; + + error = EI_GET_FD__(cbs, ctx, &fd); + if (error) + return error; + + do { + fd_set writemask; + struct timeval tv; + + tv.tv_sec = (time_t) (ms / 1000U); + ms %= 1000U; + tv.tv_usec = (time_t) (ms * 1000U); + FD_ZERO(&writemask); + FD_SET(fd,&writemask); + switch (select(fd+1, NULL, &writemask, NULL, &tv)) { + case -1 : + error = get_error(); + if (error != EINTR) + return error; + break; + case 0: + return ETIMEDOUT; /* timeout */ + default: + if (!FD_ISSET(fd, &writemask)) { + return EIO; /* Other error */ + } + error = 0; + break; + } + } while (error == EINTR); + } + do { + error = cbs->writev(ctx, (const void *) iov, iovcnt, len, ms); + } while (error == EINTR); + return error; +} + +int ei_writev_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, + const struct iovec *iov, int iovcnt, + ssize_t *len, + unsigned ms) +{ + ssize_t i, done, sum; struct iovec *iov_base = NULL; struct iovec *current_iov; int current_iovcnt; - int sum; + int fd, error; + int basic; + + if (!cbs->writev) + return ENOTSUP; + + error = EI_GET_FD__(cbs, ctx, &fd); + if (error) + return error; + basic = !(cbs->flags & EI_SCLBK_FLG_FULL_IMPL); + for (sum = 0, i = 0; i < iovcnt; ++i) { sum += iov[i].iov_len; } - if (ms != 0U) { + if (basic && ms != 0U) { SET_NONBLOCKING(fd); } current_iovcnt = iovcnt; current_iov = (struct iovec *) iov; done = 0; for (;;) { - i = ei_writev_t(fd, current_iov, current_iovcnt, ms); - if (i <= 0) { /* ei_writev_t should always return at least 1 */ + + error = writev_ctx_t__(cbs, ctx, current_iov, current_iovcnt, &i, ms); + if (error) { + *len = done; if (ms != 0U) { SET_BLOCKING(fd); } if (iov_base != NULL) { free(iov_base); } - return (i); - } + return error; + } done += i; if (done < sum) { if (iov_base == NULL) { iov_base = malloc(sizeof(struct iovec) * iovcnt); if (iov_base == NULL) { - return -1; + *len = done; + return ENOMEM; } memcpy(iov_base, iov, sizeof(struct iovec) * iovcnt); current_iov = iov_base; @@ -189,195 +475,383 @@ int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned break; } } - if (ms != 0U) { + if (basic && ms != 0U) { SET_BLOCKING(fd); } if (iov_base != NULL) { free(iov_base); } - return (sum); + *len = done; + return 0; } +#endif /* defined(EI_HAVE_STRUCT_IOVEC__) */ -#endif - -int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms) +int ei_socket_ctx__(ei_socket_callbacks *cbs, void **ctx, void *setup_ctx) { int res; - int error; - int s_res; - struct timeval tv; - fd_set writefds; - fd_set exceptfds; - - if (ms == 0) { - res = connect(fd, sinp, sin_siz); - return (res < 0) ? -1 : res; - } else { - SET_NONBLOCKING(fd); - res = connect(fd, sinp, sin_siz); - error = GET_SOCKET_ERROR(); - SET_BLOCKING(fd); - if (!MEANS_SOCKET_ERROR(res)) { - return (res < 0) ? -1 : res; - } else { - if (error != ERROR_WOULDBLOCK && - error != ERROR_INPROGRESS) { - return -1; - } else { - tv.tv_sec = (long) (ms/1000U); - ms %= 1000U; - tv.tv_usec = (long) (ms * 1000U); - FD_ZERO(&writefds); - FD_SET(fd,&writefds); - FD_ZERO(&exceptfds); - FD_SET(fd,&exceptfds); - s_res = select(fd + 1, NULL, &writefds, &exceptfds, &tv); - switch (s_res) { - case 0: - return -2; - case 1: - if (FD_ISSET(fd, &exceptfds)) { - return -1; - } else { - return 0; /* Connect completed */ - } - default: - return -1; - } - } - } - } + + do { + res = cbs->socket(ctx, setup_ctx); + } while (res == EINTR); + + return res; } -int ei_accept_t(int fd, void *addr, void *addrlen, unsigned ms) +int ei_close_ctx__(ei_socket_callbacks *cbs, void *ctx) { - int res; - if (ms != 0) { - fd_set readmask; - struct timeval tv; - tv.tv_sec = (time_t) (ms / 1000U); - ms %= 1000U; - tv.tv_usec = (time_t) (ms * 1000U); - FD_ZERO(&readmask); - FD_SET(fd,&readmask); - switch (select(fd+1, &readmask, NULL, NULL, &tv)) { - case -1 : - return -1; /* i/o error */ - case 0: - return -2; /* timeout */ - default: - if (!FD_ISSET(fd, &readmask)) { - return -1; /* Other error */ - } - } - } - res = (int) accept(fd,addr,addrlen); - return (res < 0) ? -1 : res; + return cbs->close(ctx); } + +int ei_connect_ctx_t__(ei_socket_callbacks *cbs, void *ctx, + void *addr, int len, unsigned ms) +{ + int res, fd; + + if ((cbs->flags & EI_SCLBK_FLG_FULL_IMPL) || ms == EI_SCLBK_INF_TMO) { + do { + res = cbs->connect(ctx, addr, len, ms); + } while (res == EINTR); + return res; + } + + res = EI_GET_FD__(cbs, ctx, &fd); + if (res) + return res; + SET_NONBLOCKING(fd); + do { + res = cbs->connect(ctx, addr, len, 0); + } while (res == EINTR); + SET_BLOCKING(fd); + + switch (res) { + case EINPROGRESS: + case EAGAIN: +#ifdef EWOULDBLOCK +#if EWOULDBLOCK != EAGAIN + case EWOULDBLOCK: +#endif +#endif + break; + default: + return res; + } + while (1) { + struct timeval tv; + fd_set writefds; + fd_set exceptfds; + + tv.tv_sec = (long) (ms/1000U); + ms %= 1000U; + tv.tv_usec = (long) (ms * 1000U); + FD_ZERO(&writefds); + FD_SET(fd,&writefds); + FD_ZERO(&exceptfds); + FD_SET(fd,&exceptfds); + res = select(fd + 1, NULL, &writefds, &exceptfds, &tv); + switch (res) { + case -1: + res = get_error(); + if (res != EINTR) + return res; + break; + case 0: + return ETIMEDOUT; + case 1: + if (!FD_ISSET(fd, &exceptfds)) + return 0; /* Connect completed */ + /* fall through... */ + default: + return EIO; + } + } +} -static int ei_read_t(int fd, char* buf, int len, unsigned ms) +int ei_listen_ctx__(ei_socket_callbacks *cbs, void *ctx, + void *adr, int *len, int backlog) { int res; - if (ms != 0) { - fd_set readmask; - struct timeval tv; - tv.tv_sec = (time_t) (ms / 1000U); - ms %= 1000U; - tv.tv_usec = (time_t) (ms * 1000U); - FD_ZERO(&readmask); - FD_SET(fd,&readmask); - switch (select(fd+1, &readmask, NULL, NULL, &tv)) { - case -1 : - return -1; /* i/o error */ - case 0: - return -2; /* timeout */ - default: - if (!FD_ISSET(fd, &readmask)) { - return -1; /* Other error */ - } - } + + do { + res = cbs->listen(ctx, adr, len, backlog); + } while (res == EINTR); + return res; +} + +int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx, + void *addr, int *len, unsigned ms) +{ + int error; + + if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) { + int fd; + + error = EI_GET_FD__(cbs, *ctx, &fd); + if (error) + return error; + + do { + fd_set readmask; + struct timeval tv; + + tv.tv_sec = (time_t) (ms / 1000U); + ms %= 1000U; + tv.tv_usec = (time_t) (ms * 1000U); + FD_ZERO(&readmask); + FD_SET(fd,&readmask); + switch (select(fd+1, &readmask, NULL, NULL, &tv)) { + case -1 : + error = get_error(); + if (error != EINTR) + return error; + break; + case 0: + return ETIMEDOUT; /* timeout */ + default: + if (!FD_ISSET(fd, &readmask)) { + return EIO; /* Other error */ + } + error = 0; + break; + } + } while (error == EINTR); } - res = readsocket(fd, buf, len); - return (res < 0) ? -1 : res; + do { + error = cbs->accept(ctx, addr, len, ms); + } while (error == EINTR); + return error; } -static int ei_write_t(int fd, const char* buf, int len, unsigned ms) +static int read_ctx_t__(ei_socket_callbacks *cbs, void *ctx, + char* buf, ssize_t *len, unsigned ms) { - int res; - if (ms != 0) { - fd_set writemask; - struct timeval tv; - tv.tv_sec = (time_t) (ms / 1000U); - ms %= 1000U; - tv.tv_usec = (time_t) (ms * 1000U); - FD_ZERO(&writemask); - FD_SET(fd,&writemask); - switch (select(fd+1, NULL, &writemask, NULL, &tv)) { - case -1 : - return -1; /* i/o error */ - case 0: - return -2; /* timeout */ - default: - if (!FD_ISSET(fd, &writemask)) { - return -1; /* Other error */ - } - } + int error; + + if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) { + int fd; + + error = EI_GET_FD__(cbs, ctx, &fd); + if (error) + return error; + + do { + fd_set readmask; + struct timeval tv; + + tv.tv_sec = (time_t) (ms / 1000U); + ms %= 1000U; + tv.tv_usec = (time_t) (ms * 1000U); + FD_ZERO(&readmask); + FD_SET(fd,&readmask); + switch (select(fd+1, &readmask, NULL, NULL, &tv)) { + case -1 : + error = get_error(); + if (error != EINTR) + return error; + break; + case 0: + return ETIMEDOUT; /* timeout */ + default: + if (!FD_ISSET(fd, &readmask)) { + return EIO; /* Other error */ + } + error = 0; + break; + } + } while (error == EINTR); + } + do { + error = cbs->read(ctx, buf, len, ms); + } while (error == EINTR); + return error; +} + +static int write_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char* buf, ssize_t *len, unsigned ms) +{ + int error; + + if (!(cbs->flags & EI_SCLBK_FLG_FULL_IMPL) && ms != EI_SCLBK_INF_TMO) { + int fd; + + error = EI_GET_FD__(cbs, ctx, &fd); + if (error) + return error; + + do { + fd_set writemask; + struct timeval tv; + + tv.tv_sec = (time_t) (ms / 1000U); + ms %= 1000U; + tv.tv_usec = (time_t) (ms * 1000U); + FD_ZERO(&writemask); + FD_SET(fd,&writemask); + switch (select(fd+1, NULL, &writemask, NULL, &tv)) { + case -1 : + error = get_error(); + if (error != EINTR) + return error; + break; + case 0: + return ETIMEDOUT; /* timeout */ + default: + if (!FD_ISSET(fd, &writemask)) { + return EIO; /* Other error */ + } + error = 0; + break; + } + } while (error == EINTR); } - res = writesocket(fd, buf, len); - return (res < 0) ? -1 : res; + do { + error = cbs->write(ctx, buf, len, ms); + } while (error == EINTR); + return error; } /* * Fill buffer, return buffer length, 0 for EOF, < 0 (and sets errno) * for error. */ -int ei_read_fill_t(int fd, char* buf, int len, unsigned ms) +int ei_read_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len, unsigned ms) { - int i,got=0; + ssize_t got = 0; + ssize_t want = *len; do { - i = ei_read_t(fd, buf+got, len-got, ms); - if (i <= 0) - return (i); - got += i; - } while (got < len); - return (len); - + ssize_t read_len = want-got; + int error; + + do { + error = read_ctx_t__(cbs, ctx, buf+got, &read_len, ms); + } while (error == EINTR); + if (error) + return error; + if (read_len == 0) { + *len = got; + return 0; + } + got += read_len; + } while (got < want); + + *len = got; + return 0; } /* read_fill */ -int ei_read_fill(int fd, char* buf, int len) +int ei_read_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len) { - return ei_read_fill_t(fd, buf, len, 0); + return ei_read_fill_ctx_t__(cbs, ctx, buf, len, 0); } /* write entire buffer on fd or fail (setting errno) */ -int ei_write_fill_t(int fd, const char *buf, int len, unsigned ms) +int ei_write_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len, unsigned ms) { - int i,done=0; - if (ms != 0U) { + ssize_t tot = *len, done = 0; + int error, fd = -1, basic = !(cbs->flags & EI_SCLBK_FLG_FULL_IMPL); + + if (basic && ms != 0U) { + error = EI_GET_FD__(cbs, ctx, &fd); + if (error) + return error; SET_NONBLOCKING(fd); } do { - i = ei_write_t(fd, buf+done, len-done, ms); - if (i <= 0) { - if (ms != 0U) { + ssize_t write_len = tot-done; + error = write_ctx_t__(cbs, ctx, buf+done, &write_len, ms); + if (error) { + *len = done; + if (basic && ms != 0U) { SET_BLOCKING(fd); } - return (i); + return error; } - done += i; - } while (done < len); - if (ms != 0U) { + done += write_len; + } while (done < tot); + if (basic && ms != 0U) { SET_BLOCKING(fd); } - return (len); + *len = done; + return 0; +} + +int ei_write_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len) +{ + return ei_write_fill_ctx_t__(cbs, ctx, buf, len, 0); +} + +/* + * Internal API for TCP/IPv4 + */ + +int ei_connect_t__(int fd, void *addr, int len, unsigned ms) +{ + return ei_connect_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + addr, len, ms); } -int ei_write_fill(int fd, const char *buf, int len) +int ei_socket__(int *fd) { - return ei_write_fill_t(fd, buf, len, 0); + void *ctx; + int error = ei_socket_ctx__(&ei_default_socket_callbacks, &ctx, NULL); + if (error) + return error; + return EI_GET_FD__(&ei_default_socket_callbacks, ctx, fd); } +int ei_close__(int fd) +{ + return ei_close_ctx__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd)); +} + +int ei_listen__(int fd, void *adr, int *len, int backlog) +{ + return ei_listen_ctx__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + adr, len, backlog); +} + +int ei_accept_t__(int *fd, void *addr, int *len, unsigned ms) +{ + void *ctx = EI_FD_AS_CTX__(*fd); + int error = ei_accept_ctx_t__(&ei_default_socket_callbacks, &ctx, + addr, len, ms); + if (error) + return error; + return EI_GET_FD__(&ei_default_socket_callbacks, ctx, fd); +} + +int ei_read_fill_t__(int fd, char* buf, ssize_t *len, unsigned ms) +{ + return ei_read_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + buf, len, ms); +} + +int ei_read_fill__(int fd, char* buf, ssize_t *len) +{ + return ei_read_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + buf, len, 0); +} + +int ei_write_fill_t__(int fd, const char *buf, ssize_t *len, unsigned ms) +{ + return ei_write_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + buf, len, ms); +} + +int ei_write_fill__(int fd, const char *buf, ssize_t *len) +{ + return ei_write_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + buf, len, 0); +} + +#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV) + +int ei_writev_fill_t__(int fd, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms) +{ + return ei_writev_fill_ctx_t__(&ei_default_socket_callbacks, EI_FD_AS_CTX__(fd), + iov, iovcnt, len, ms); +} + +#endif + diff --git a/lib/erl_interface/src/misc/ei_portio.h b/lib/erl_interface/src/misc/ei_portio.h index bded811a35..a84b5ca09c 100644 --- a/lib/erl_interface/src/misc/ei_portio.h +++ b/lib/erl_interface/src/misc/ei_portio.h @@ -21,21 +21,94 @@ */ #ifndef _EI_PORTIO_H #define _EI_PORTIO_H -#if !defined(__WIN32__) && !defined(VXWORKS) -#ifdef HAVE_WRITEV + +#undef EI_HAVE_STRUCT_IOVEC__ +#if !defined(__WIN32__) && !defined(VXWORKS) && defined(HAVE_SYS_UIO_H) /* Declaration of struct iovec *iov should be visible in this scope. */ -#include <sys/uio.h> +# include <sys/uio.h> +# define EI_HAVE_STRUCT_IOVEC__ #endif + +/* + * Internal API. Should not be used outside of the erl_interface application... + */ + +int ei_socket_ctx__(ei_socket_callbacks *cbs, void **ctx, void *setup); +int ei_close_ctx__(ei_socket_callbacks *cbs, void *ctx); +int ei_listen_ctx__(ei_socket_callbacks *cbs, void *ctx, void *adr, int *len, int backlog); +int ei_accept_ctx_t__(ei_socket_callbacks *cbs, void **ctx, void *addr, int *len, unsigned ms); +int ei_connect_ctx_t__(ei_socket_callbacks *cbs, void *ctx, void *addr, int len, unsigned ms); +int ei_read_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len); +int ei_write_fill_ctx__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len); +int ei_read_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, char* buf, ssize_t *len, unsigned ms); +int ei_write_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const char *buf, ssize_t *len, unsigned ms); +#if defined(EI_HAVE_STRUCT_IOVEC__) +int ei_writev_fill_ctx_t__(ei_socket_callbacks *cbs, void *ctx, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms); +int ei_socket_callbacks_have_writev__(ei_socket_callbacks *cbs); #endif -int ei_accept_t(int fd, void *addr, void *addrlen, unsigned ms); -int ei_connect_t(int fd, void *sinp, int sin_siz, unsigned ms); -int ei_read_fill(int fd, char* buf, int len); -int ei_write_fill(int fd, const char *buf, int len); -int ei_read_fill_t(int fd, char* buf, int len, unsigned ms); -int ei_write_fill_t(int fd, const char *buf, int len, unsigned ms); -#ifdef HAVE_WRITEV -int ei_writev_fill_t(int fd, const struct iovec *iov, int iovcnt, unsigned ms); +ei_socket_callbacks ei_default_socket_callbacks; + +#define EI_FD_AS_CTX__(FD) \ + ((void *) (long) (FD)) + +#define EI_DFLT_CTX_TO_FD__(CTX, FD) \ + ((int) (long) (CTX) < 0 \ + ? EBADF \ + : (*(FD) = (int) (long) (CTX), 0)) + +#define EI_GET_FD__(CBS, CTX, FD) \ + ((CBS) == &ei_default_socket_callbacks \ + ? EI_DFLT_CTX_TO_FD__((CTX), FD) \ + : (CBS)->get_fd((CTX), (FD))) + +extern int ei_plugin_socket_impl__; + +#if !defined(_REENTRANT) + +#define EI_HAVE_PLUGIN_SOCKET_IMPL__ \ + ei_plugin_socket_impl__ +#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ \ + ei_plugin_socket_impl__ = 1 + +#elif ((ETHR_HAVE___atomic_load_n & SIZEOF_INT) \ + && (ETHR_HAVE___atomic_store_n & SIZEOF_INT)) + +#define EI_HAVE_PLUGIN_SOCKET_IMPL__ \ + __atomic_load_n(&ei_plugin_socket_impl__, __ATOMIC_ACQUIRE) +#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ \ + __atomic_store_n(&ei_plugin_socket_impl__, 1, __ATOMIC_RELEASE) + +#else + +/* No gcc atomics; always lookup using ei_get_cbs_ctx()... */ +#define EI_HAVE_PLUGIN_SOCKET_IMPL__ 0 +#define EI_SET_HAVE_PLUGIN_SOCKET_IMPL__ (void) 0 + +#endif + +#define EI_GET_CBS_CTX__(CBS, CTX, FD) \ + (EI_HAVE_PLUGIN_SOCKET_IMPL__ \ + ? ei_get_cbs_ctx__((CBS), (CTX), (FD)) \ + : ((FD) < 0 \ + ? EBADF \ + : (*(CBS) = &ei_default_socket_callbacks, \ + *(CTX) = EI_FD_AS_CTX__((FD)), \ + 0))) +/* + * The following uses our own TCP/IPv4 socket implementation... + */ +int ei_socket__(int *fd); +int ei_close__(int fd); +int ei_listen__(int fd, void *adr, int *len, int backlog); +int ei_accept_t__(int *fd, void *addr, int *len, unsigned ms); +int ei_connect_t__(int fd, void *addr, int len, unsigned ms); +int ei_read_fill__(int fd, char* buf, ssize_t *len); +int ei_write_fill__(int fd, const char *buf, ssize_t *len); +int ei_read_fill_t__(int fd, char* buf, ssize_t *len, unsigned ms); +int ei_write_fill_t__(int fd, const char *buf, ssize_t *len, unsigned ms); +#if defined(EI_HAVE_STRUCT_IOVEC__) && defined(HAVE_WRITEV) +int ei_writev_fill_t__(int fd, const struct iovec *iov, int iovcnt, ssize_t *len, unsigned ms); #endif #endif /* _EI_PORTIO_H */ diff --git a/lib/erl_interface/src/not_used/send_link.c b/lib/erl_interface/src/not_used/send_link.c index 7be476fd93..38fae27df4 100644 --- a/lib/erl_interface/src/not_used/send_link.c +++ b/lib/erl_interface/src/not_used/send_link.c @@ -50,6 +50,7 @@ static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to, char *s; int index = 0; int n; + unsigned tmo = ms == 0 ? EI_SCLBK_INF_TMO : ms; index = 5; /* max sizes: */ ei_encode_version(msgbuf,&index); /* 1 */ @@ -69,7 +70,7 @@ static int link_unlink(int fd, const erlang_pid *from, const erlang_pid *to, if (ei_trace_distribution > 1) ei_show_sendmsg(stderr,msgbuf,NULL); #endif - n = ei_write_fill_t(fd,msgbuf,index,ms); + n = ei_write_fill_t__(fd,msgbuf,index,tmo); return (n==index ? 0 : -1); } diff --git a/lib/erl_interface/test/ei_accept_SUITE.erl b/lib/erl_interface/test/ei_accept_SUITE.erl index 78a433d21b..9c9c3f86b6 100644 --- a/lib/erl_interface/test/ei_accept_SUITE.erl +++ b/lib/erl_interface/test/ei_accept_SUITE.erl @@ -81,12 +81,10 @@ ei_accept(Config) when is_list(Config) -> ei_threaded_accept(Config) when is_list(Config) -> Einode = filename:join(proplists:get_value(data_dir, Config), "eiaccnode"), - N = 1, % 3, + N = 3, Host = atom_to_list(node()), - Port = 6767, - start_einode(Einode, N, Host, Port), + start_einode(Einode, N, Host), io:format("started eiaccnode"), - %%spawn_link(fun() -> start_einode(Einode, N, Host, Port) end), TestServerPid = self(), [spawn_link(fun() -> send_rec_einode(I, TestServerPid) end) || I <- lists:seq(0, N-1)], [receive I -> ok end || I <- lists:seq(0, N-1) ], @@ -159,10 +157,9 @@ send_rec_einode(N, TestServerPid) -> ct:fail(EINode) end. -start_einode(Einode, N, Host, Port) -> +start_einode(Einode, N, Host) -> Einodecmd = Einode ++ " " ++ atom_to_list(erlang:get_cookie()) - ++ " " ++ integer_to_list(N) ++ " " ++ Host ++ " " - ++ integer_to_list(Port) ++ " nothreads", + ++ " " ++ integer_to_list(N) ++ " " ++ Host, io:format("Einodecmd ~p ~n", [Einodecmd]), open_port({spawn, Einodecmd}, []), ok. diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c index 50df848b69..f41d741609 100644 --- a/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c +++ b/lib/erl_interface/test/ei_accept_SUITE_data/ei_accept_test.c @@ -125,45 +125,26 @@ static void cmd_ei_connect_init(char* buf, int len) ei_x_free(&res); } -static int my_listen(int port) -{ - int listen_fd; - struct sockaddr_in addr; - const char *on = "1"; - - if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - return -1; - - setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on)); - - memset((void*) &addr, 0, (size_t) sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = htonl(INADDR_ANY); - - if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) - return -1; - - listen(listen_fd, 5); - return listen_fd; -} - static void cmd_ei_publish(char* buf, int len) { int index = 0; - int listen, r; - long port; + int iport, lfd, r; + long lport; ei_x_buff x; int i; /* get port */ - if (ei_decode_long(buf, &index, &port) < 0) + if (ei_decode_long(buf, &index, &lport) < 0) fail("expected int (port)"); /* Make a listen socket */ - if ((listen = my_listen(port)) <= 0) + + iport = (int) lport; + lfd = ei_listen(&ec, &iport, 5); + if (lfd < 0) fail("listen"); + lport = (long) iport; - if ((i = ei_publish(&ec, port)) == -1) + if ((i = ei_publish(&ec, lport)) == -1) fail("ei_publish"); #ifdef VXWORKS save_fd(i); @@ -171,7 +152,7 @@ static void cmd_ei_publish(char* buf, int len) /* send listen-fd, result and errno */ ei_x_new_with_version(&x); ei_x_encode_tuple_header(&x, 3); - ei_x_encode_long(&x, listen); + ei_x_encode_long(&x, (long) lfd); ei_x_encode_long(&x, i); ei_x_encode_long(&x, erl_errno); send_bin_term(&x); diff --git a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c index 308f843530..c850d20f3c 100644 --- a/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c +++ b/lib/erl_interface/test/ei_accept_SUITE_data/eiaccnode.c @@ -47,8 +47,6 @@ #define MAIN main #endif -static int my_listen(int port); - /* A small einode. To be called from the test case ei_accept_SUITE:multi_thread @@ -64,7 +62,6 @@ static int my_listen(int port); */ static const char* cookie, * desthost; -static int port; /* actually base port */ #ifndef SD_SEND #ifdef SHUTWR @@ -74,10 +71,6 @@ static int port; /* actually base port */ #endif #endif -#ifndef __WIN32__ -#define closesocket(fd) close(fd) -#endif - #ifdef __WIN32__ static DWORD WINAPI #else @@ -86,26 +79,32 @@ static void* einode_thread(void* num) { int n = (int)num; + int port; ei_cnode ec; - char myname[100], destname[100]; + char myname[100], destname[100], filename[100]; int r, fd, listen; ErlConnect conn; erlang_msg msg; -/* FILE* f;*/ + FILE* file; - sprintf(myname, "eiacc%d", n); - printf("thread %d (%s) listening\n", n, myname, destname); + sprintf(filename, "eiacc%d_trace.txt", n); + file = fopen(filename, "w"); + + sprintf(myname, "eiacc%d", n); fflush(file); r = ei_connect_init(&ec, myname, cookie, 0); - if ((listen = my_listen(port+n)) <= 0) { - printf("listen err\n"); + port = 0; + listen = ei_listen(&ec, &port, 5); + if (listen <= 0) { + fprintf(file, "listen err\n"); fflush(file); exit(7); } - if (ei_publish(&ec, port + n) == -1) { - printf("ei_publish port %d\n", port+n); + fprintf(file, "thread %d (%s:%s) listening on port %d\n", n, myname, destname, port); + if (ei_publish(&ec, port) == -1) { + fprintf(file, "ei_publish port %d\n", port+n); fflush(file); exit(8); } fd = ei_accept(&ec, listen, &conn); - printf("ei_accept %d\n", fd); + fprintf(file, "ei_accept %d\n", fd); fflush(file); if (fd >= 0) { ei_x_buff x, xs; int index, version; @@ -117,37 +116,38 @@ static void* if (got == ERL_TICK) continue; if (got == ERL_ERROR) { - printf("receive error %d\n", n); + fprintf(file, "receive error %d\n", n); fflush(file); return 0; } - printf("received %d\n", got); + fprintf(file, "received %d\n", got); fflush(file); break; } index = 0; if (ei_decode_version(x.buff, &index, &version) != 0) { - printf("ei_decode_version %d\n", n); + fprintf(file, "ei_decode_version %d\n", n); fflush(file); return 0; } if (ei_decode_pid(x.buff, &index, &pid) != 0) { - printf("ei_decode_pid %d\n", n); + fprintf(file, "ei_decode_pid %d\n", n); fflush(file); return 0; } -/* fprintf(f, "got pid from %s \n", pid.node);*/ + fprintf(file, "got pid from %s \n", pid.node); fflush(file); ei_x_new_with_version(&xs); ei_x_encode_tuple_header(&xs, 2); ei_x_encode_long(&xs, n); ei_x_encode_pid(&xs, &pid); r = ei_send(fd, &pid, xs.buff, xs.index); -/* fprintf(f, "sent %d bytes %d\n", xs.index, r);*/ + fprintf(file, "sent %d bytes %d\n", xs.index, r); fflush(file); shutdown(fd, SD_SEND); - closesocket(fd); + ei_close_connection(fd); ei_x_free(&x); ei_x_free(&xs); } else { - printf("coudn't connect fd %d r %d\n", fd, r); + fprintf(file, "coudn't connect fd %d r %d\n", fd, r); fflush(file); } - printf("done thread %d\n", n); -/* fclose(f);*/ + ei_close_connection(listen); + fprintf(file, "done thread %d\n", n); + fclose(file); return 0; } @@ -170,12 +170,14 @@ MAIN(int argc, char *argv[]) if (n > 100) exit(2); desthost = argv[3]; - port = atoi(argv[4]); -#ifndef VXWORKS - no_threads = argv[5] != NULL && strcmp(argv[5], "nothreads") == 0; -#else + if (argc == 3) + no_threads = 0; + else + no_threads = argv[4] != NULL && strcmp(argv[4], "nothreads") == 0; +#ifdef VXWORKS no_threads = 1; #endif + for (i = 0; i < n; ++i) { if (!no_threads) { #ifndef VXWORKS @@ -209,27 +211,3 @@ MAIN(int argc, char *argv[]) printf("ok\n"); return 0; } - -static int my_listen(int port) -{ - int listen_fd; - struct sockaddr_in addr; - const char *on = "1"; - - if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) - return -1; - - setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on)); - - memset((void*) &addr, 0, (size_t) sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - addr.sin_addr.s_addr = htonl(INADDR_ANY); - - if (bind(listen_fd, (struct sockaddr*) &addr, sizeof(addr)) < 0) - return -1; - - listen(listen_fd, 5); - return listen_fd; -} - diff --git a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c index bead0f8413..b87feb9dfc 100644 --- a/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c +++ b/lib/erl_interface/test/erl_eterm_SUITE_data/cnode.c @@ -20,7 +20,7 @@ #include <stdlib.h> #include <stdio.h> - +#include <string.h> #include "ei.h" #include "erl_interface.h" @@ -68,6 +68,7 @@ MAIN(int argc, char **argv) char host[80]; int number; ETERM *ref, *ref1, *ref2; + FILE *dfile = fopen("cnode_debug_printout", "w"); erl_init(NULL, 0); @@ -80,28 +81,30 @@ MAIN(int argc, char **argv) gethostname(host, sizeof(host)); sprintf(node, "c%d@%s", number, host); - printf("s = %d\n", s); + fprintf(dfile, "s = %d\n", s); fflush(dfile); sprintf(server, "test_server@%s", host); fd = erl_connect(server); - printf("fd = %d\n", fd); + fprintf(dfile, "fd = %d\n", fd); -/* printf("dist = %d\n", erl_distversion(fd)); */ +/* fprintf(dfile, "dist = %d\n", erl_distversion(fd)); */ #if 1 ref = erl_mk_long_ref(node, 4711, 113, 98, 0); #else ref = erl_mk_ref(node, 4711, 0); #endif - printf("ref = %d\n", ref); + fprintf(dfile, "ref = %p\n", ref); fflush(dfile); s = erl_reg_send(fd, "mip", ref); - printf("s = %d\n", s); + fprintf(dfile, "s = %d\n", s); fflush(dfile); { ETERM* emsg; emsg = SELF(fd); - erl_reg_send(fd,"mip",emsg); + fprintf(dfile, "pid = %p\n", emsg); fflush(dfile); + s = erl_reg_send(fd,"mip",emsg); + fprintf(dfile, "s2 = %d\n", s); fflush(dfile); erl_free_term(emsg); } @@ -116,28 +119,29 @@ MAIN(int argc, char **argv) #endif switch (s) { case ERL_TICK: - printf("tick\n"); + fprintf(dfile, "tick\n"); break; case ERL_ERROR: - printf("error\n"); + fprintf(dfile, "error: %s (%d)\n", strerror(erl_errno), erl_errno); break; case ERL_MSG: - printf("msg %d\n", msgsize); + fprintf(dfile, "msg %d\n", msgsize); break; default: - printf("unknown result %d\n", s); + fprintf(dfile, "unknown result %d\n", s); break; } + fflush(dfile); } while (s == ERL_TICK); s = erl_reg_send(fd, "mip", msg.msg); - printf("s = %d\n", s); + fprintf(dfile, "s = %d\n", s); fflush(dfile); s = erl_reg_send(fd, "mip", msg.to); - printf("s = %d\n", s); + fprintf(dfile, "s = %d\n", s); fflush(dfile); #if 0 /* from = NULL! */ s = erl_reg_send(fd, "mip", msg.from); - printf("s = %d\n", s); + fprintf(dfile, "s = %d\n", s); fflush(dfile); #endif #if 0 @@ -150,17 +154,19 @@ MAIN(int argc, char **argv) ref1 = erl_mk_long_ref(node, 4711, 113, 98, 0); ref2 = erl_mk_ref(node, 4711, 0); s = erl_encode(ref1, buf1); - printf("enc1 s = %d\n", s); + fprintf(dfile, "enc1 s = %d\n", s); fflush(dfile); s = erl_encode(ref2, buf2); - printf("enc2 s = %d\n", s); + fprintf(dfile, "enc2 s = %d\n", s); fflush(dfile); s = erl_compare_ext(buf1, buf2); - printf("comp s = %d\n", s); + fprintf(dfile, "comp s = %d\n", s); fflush(dfile); /* Compare, in another way */ s = erl_match(ref1, ref2); - printf("match s = %d\n", s); + fprintf(dfile, "match s = %d\n", s); fflush(dfile); #endif + fclose(dfile); + erl_close_connection(fd); return 0; diff --git a/lib/et/doc/src/et.xml b/lib/et/doc/src/et.xml index 3009b559e1..a362d00b3e 100644 --- a/lib/et/doc/src/et.xml +++ b/lib/et/doc/src/et.xml @@ -32,14 +32,14 @@ <rev>%VSN%</rev> <file>et</file> </header> - <module>et</module> + <module since="">et</module> <modulesummary>Main API of the Event Trace (ET) application</modulesummary> <description> <p>Interface module for the Event Trace (ET) application</p> </description> <funcs> <func> - <name>trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name> + <name since="OTP R13B04">trace_me(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name> <fsummary>A function that is intended to be traced.</fsummary> <type> <v>DetailLevel = integer(X) when X =< 0, X >= 100</v> @@ -70,7 +70,7 @@ </func> <func> - <name>trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name> + <name since="OTP R13B04">trace_me(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name> <fsummary>A function that is intended to be traced.</fsummary> <desc> <p>Invokes <c>et:trace_me/5</c> with both <c>From</c> and <c>To</c> @@ -79,8 +79,8 @@ </func> <func> - <name>phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name> - <name>phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name> + <name since="">phone_home(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name> + <name since="">phone_home(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name> <fsummary>Send a signal to the outer space</fsummary> <desc> <p>These functions sends a signal to the outer space and the @@ -90,8 +90,8 @@ </desc> </func> <func> - <name>report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name> - <name>report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name> + <name since="">report_event(DetailLevel, FromTo, Label, Contents) -> hopefully_traced</name> + <name since="">report_event(DetailLevel, From, To, Label, Contents) -> hopefully_traced</name> <fsummary>Deprecated functions</fsummary> <desc> <p>Deprecated functions which for the time being are kept for diff --git a/lib/et/doc/src/et_collector.xml b/lib/et/doc/src/et_collector.xml index fd90ecfc41..f908612797 100644 --- a/lib/et/doc/src/et_collector.xml +++ b/lib/et/doc/src/et_collector.xml @@ -32,14 +32,14 @@ <rev>%VSN%</rev> <file>et_collector.xml</file> </header> - <module>et_collector</module> + <module since="">et_collector</module> <modulesummary>Collect trace events and provide a backing storage appropriate for iteration </modulesummary> <description> <p>Interface module for the Event Trace (ET) application</p> </description> <funcs> <func> - <name>start_link(Options) -> {ok, CollectorPid} | {error, Reason}</name> + <name since="">start_link(Options) -> {ok, CollectorPid} | {error, Reason}</name> <fsummary>Start a collector process</fsummary> <type> <v>Options = [option()]</v> @@ -105,7 +105,7 @@ </desc> </func> <func> - <name>stop(CollectorPid) -> ok</name> + <name since="">stop(CollectorPid) -> ok</name> <fsummary>Stop a collector process</fsummary> <type> <v>CollectorPid = pid()</v> @@ -115,7 +115,7 @@ </desc> </func> <func> - <name>save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}</name> + <name since="">save_event_file(CollectorPid, FileName, Options) -> ok | {error, Reason}</name> <fsummary>Save the events to a file</fsummary> <type> <v>CollectorPid = pid()</v> @@ -139,7 +139,7 @@ </desc> </func> <func> - <name>load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name> + <name since="">load_event_file(CollectorPid, FileName) -> {ok, BadBytes} | exit(Reason)</name> <fsummary>Load the event table from a file</fsummary> <type> <v>CollectorPid = pid()</v> @@ -152,9 +152,9 @@ </desc> </func> <func> - <name>report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name> - <name>report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name> - <name>report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name> + <name since="">report(Handle, TraceOrEvent) -> {ok, Continuation} | exit(Reason)</name> + <name since="">report_event(Handle, DetailLevel, FromTo, Label, Contents) -> {ok, Continuation} | exit(Reason)</name> + <name since="">report_event(Handle, DetailLevel, From, To, Label, Contents) -> {ok, Continuation} | exit(Reason)</name> <fsummary>Report an event to the collector</fsummary> <type> <v>Handle = Initial | Continuation</v> @@ -181,7 +181,7 @@ </desc> </func> <func> - <name>make_key(Type, Stuff) -> Key</name> + <name since="">make_key(Type, Stuff) -> Key</name> <fsummary>Make a key out of an event record or an old key</fsummary> <type> <v>Type = record(table_handle) | trace_ts | event_ts</v> @@ -193,7 +193,7 @@ </desc> </func> <func> - <name>get_table_handle(CollectorPid) -> Handle</name> + <name since="">get_table_handle(CollectorPid) -> Handle</name> <fsummary>Return a table handle</fsummary> <type> <v>CollectorPid = pid()</v> @@ -204,7 +204,7 @@ </desc> </func> <func> - <name>get_global_pid() -> CollectorPid | exit(Reason)</name> + <name since="">get_global_pid() -> CollectorPid | exit(Reason)</name> <fsummary>Return a the identity of the globally registered collector if there is any</fsummary> <type> <v>CollectorPid = pid()</v> @@ -216,7 +216,7 @@ </desc> </func> <func> - <name>change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}</name> + <name since="">change_pattern(CollectorPid, RawPattern) -> {old_pattern, TracePattern}</name> <fsummary>Change active trace pattern globally on all trace nodes</fsummary> <type> <v>CollectorPid = pid()</v> @@ -232,9 +232,9 @@ </desc> </func> <func> - <name>dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok</name> - <name>dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok</name> - <name>dict_insert(CollectorPid, Key, Val) -> ok</name> + <name since="">dict_insert(CollectorPid, {filter, collector}, FilterFun) -> ok</name> + <name since="">dict_insert(CollectorPid, {subscriber, SubscriberPid}, Void) -> ok</name> + <name since="">dict_insert(CollectorPid, Key, Val) -> ok</name> <fsummary>Insert a dictionary entry and send a {et, {dict_insert, Key, Val}} tuple to all registered subscribers.</fsummary> <type> <v>CollectorPid = pid()</v> @@ -259,7 +259,7 @@ </desc> </func> <func> - <name>dict_lookup(CollectorPid, Key) -> [Val]</name> + <name since="">dict_lookup(CollectorPid, Key) -> [Val]</name> <fsummary>Lookup a dictionary entry and return zero or one value</fsummary> <type> <v>CollectorPid = pid()</v> @@ -273,7 +273,7 @@ </desc> </func> <func> - <name>dict_delete(CollectorPid, Key) -> ok</name> + <name since="">dict_delete(CollectorPid, Key) -> ok</name> <fsummary>Delete a dictionary entry and send a {et, {dict_delete, Key}} tuple to all registered subscribers.</fsummary> <type> <v>CollectorPid = pid()</v> @@ -290,7 +290,7 @@ </desc> </func> <func> - <name>dict_match(CollectorPid, Pattern) -> [Match]</name> + <name since="">dict_match(CollectorPid, Pattern) -> [Match]</name> <fsummary>Match some dictionary entries</fsummary> <type> <v>CollectorPid = pid()</v> @@ -306,7 +306,7 @@ </desc> </func> <func> - <name>multicast(_CollectorPid, Msg) -> ok</name> + <name since="">multicast(_CollectorPid, Msg) -> ok</name> <fsummary>Sends a message to all registered subscribers</fsummary> <type> <v>CollectorPid = pid()</v> @@ -318,7 +318,7 @@ </desc> </func> <func> - <name>start_trace_client(CollectorPid, Type, Parameters) -> file_loaded | {trace_client_pid, pid()} | exit(Reason)</name> + <name since="">start_trace_client(CollectorPid, Type, Parameters) -> file_loaded | {trace_client_pid, pid()} | exit(Reason)</name> <fsummary>Load raw Erlang trace from a file, port or process.</fsummary> <type> <v>Type = dbg_trace_client_type()</v> @@ -330,14 +330,14 @@ </desc> </func> <func> - <name>iterate(Handle, Prev, Limit) -> NewAcc</name> + <name since="">iterate(Handle, Prev, Limit) -> NewAcc</name> <fsummary>Iterates over the currently stored events</fsummary> <desc> <p>Short for iterate(Handle, Prev, Limit, undefined, Prev) -> NewAcc</p> </desc> </func> <func> - <name>iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc</name> + <name since="">iterate(Handle, Prev, Limit, Fun, Acc) -> NewAcc</name> <fsummary>Iterate over the currently stored events</fsummary> <type> <v>Handle = collector_pid() | table_handle()</v> @@ -361,7 +361,7 @@ </desc> </func> <func> - <name>clear_table(Handle) -> ok</name> + <name since="">clear_table(Handle) -> ok</name> <fsummary>Clear the event table</fsummary> <type> <v>Handle = collector_pid() | table_handle()</v> diff --git a/lib/et/doc/src/et_selector.xml b/lib/et/doc/src/et_selector.xml index 30ca74c872..3c766cafb7 100644 --- a/lib/et/doc/src/et_selector.xml +++ b/lib/et/doc/src/et_selector.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>et_selector.xml</file> </header> - <module>et_selector</module> + <module since="">et_selector</module> <modulesummary>Define event transforms and trace patterns</modulesummary> <description> <p></p> @@ -40,7 +40,7 @@ <funcs> <func> - <name>make_pattern(RawPattern) -> TracePattern</name> + <name since="">make_pattern(RawPattern) -> TracePattern</name> <fsummary>Makes a trace pattern suitable to feed change_pattern/1</fsummary> <type> @@ -61,7 +61,7 @@ </func> <func> - <name>change_pattern(Pattern) -> ok</name> + <name since="">change_pattern(Pattern) -> ok</name> <fsummary>Activates/deactivates tracing by changing the current trace pattern</fsummary> @@ -85,7 +85,7 @@ </desc> </func> <func> - <name>parse_event(Mod, ValidTraceData) -> false | true | {true, Event}</name> + <name since="">parse_event(Mod, ValidTraceData) -> false | true | {true, Event}</name> <fsummary>Transforms trace data and makes an event record out of it</fsummary> diff --git a/lib/et/doc/src/et_viewer.xml b/lib/et/doc/src/et_viewer.xml index e0b39636e9..9d59eef668 100644 --- a/lib/et/doc/src/et_viewer.xml +++ b/lib/et/doc/src/et_viewer.xml @@ -32,14 +32,14 @@ <rev>%VSN%</rev> <file>et_viewer.xml</file> </header> - <module>et_viewer</module> + <module since="">et_viewer</module> <modulesummary>Displays a sequence chart for trace events (messages/actions)</modulesummary> <description> <p></p> </description> <funcs> <func> - <name>file(FileName) -> {ok, ViewerPid} | {error, Reason}</name> + <name since="">file(FileName) -> {ok, ViewerPid} | {error, Reason}</name> <fsummary>Start a new event viewer and a corresponding collector and load them with trace events from a trace file.</fsummary> <type> <v>FileName() = string()</v> @@ -52,7 +52,7 @@ </desc> </func> <func> - <name>start() -> ok</name> + <name since="">start() -> ok</name> <fsummary>Simplified start of a sequence chart viewer with global tracing activated.</fsummary> <desc> <p>Simplified start of a sequence chart viewer with @@ -62,7 +62,7 @@ </desc> </func> <func> - <name>start(Options) -> ok</name> + <name since="">start(Options) -> ok</name> <fsummary>Start of a sequence chart viewer without linking to the parent process.</fsummary> <desc> <p>Start of a sequence chart viewer without linking @@ -70,7 +70,7 @@ </desc> </func> <func> - <name>start_link(Options) -> {ok, ViewerPid} | {error, Reason}</name> + <name since="">start_link(Options) -> {ok, ViewerPid} | {error, Reason}</name> <fsummary>Start a sequence chart viewer for trace events (messages/actions)</fsummary> <type> <v>Options = [option() | collector_option()]</v> @@ -125,7 +125,7 @@ </desc> </func> <func> - <name>get_collector_pid(ViewerPid) -> CollectorPid</name> + <name since="">get_collector_pid(ViewerPid) -> CollectorPid</name> <fsummary>Returns the identifier of the collector process</fsummary> <type> <v>ViewerPid = pid()</v> @@ -136,7 +136,7 @@ </desc> </func> <func> - <name>stop(ViewerPid) -> ok</name> + <name since="">stop(ViewerPid) -> ok</name> <fsummary>Stops a viewer</fsummary> <type> <v>ViewerPid = pid()</v> diff --git a/lib/et/doc/src/notes.xml b/lib/et/doc/src/notes.xml index 110d3b2110..e144defb69 100644 --- a/lib/et/doc/src/notes.xml +++ b/lib/et/doc/src/notes.xml @@ -37,6 +37,23 @@ one section in this document. The title of each section is the version number of <c>Event Tracer (ET)</c>.</p> +<section><title>ET 1.6.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The scroll bar of the et_viewer window could not be + dragged all the way to the top of the window. It would + always stop at the second event. This is now corrected.</p> + <p> + Own Id: OTP-15463 Aux Id: ERL-780 </p> + </item> + </list> + </section> + +</section> + <section><title>ET 1.6.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/et/src/et_wx_viewer.erl b/lib/et/src/et_wx_viewer.erl index 4dd44e7a4c..041527fec4 100644 --- a/lib/et/src/et_wx_viewer.erl +++ b/lib/et/src/et_wx_viewer.erl @@ -793,8 +793,8 @@ handle_info(#wx{event = #wxScroll{type = scroll_changed}} = Wx, S) -> N = round(S#state.n_events * Pos / Range), Diff = case N - event_pos(S) of - D when D < 0 -> D - 1; - D -> D + 1 + D when D < 0 -> D; + D -> D end, S2 = scroll_changed(S, Diff), noreply(S2); @@ -1002,7 +1002,7 @@ scroll_changed(S, Expected) -> scroll_first(S); last -> scroll_last(S) - end; + end; true -> %% Down OldPos = event_pos(S), @@ -1018,19 +1018,24 @@ scroll_changed(S, Expected) -> end. jump_up(S, OldKey, OldPos, NewPos) -> - Try = NewPos - OldPos, + Try = NewPos - OldPos -1, Order = S#state.event_order, - Fun = fun(Event, #e{pos = P}) when P >= NewPos -> - Key = et_collector:make_key(Order, Event), - #e{event = Event, key = Key, pos = P - 1}; - (_, Acc) -> - Acc - end, - PrevE = et_collector:iterate(S#state.collector_pid, - OldKey, - Try, - Fun, - #e{key = OldKey, pos = OldPos}), + PrevE = + if NewPos =:= 0 -> + first; + true -> + Fun = fun(Event, #e{pos = P}) when P >= NewPos -> + Key = et_collector:make_key(Order, Event), + #e{event = Event, key = Key, pos = P - 1}; + (_E, Acc) -> + Acc + end, + et_collector:iterate(S#state.collector_pid, + OldKey, + Try, + Fun, + #e{key = OldKey, pos = OldPos}) + end, case collect_more_events(S, PrevE, S#state.events_per_page) of {_, []} -> S; @@ -2013,7 +2018,7 @@ update_scroll_bar(#state{scroll_bar = ScrollBar, PixelsPerEvent = Range / EventsPerPage, Share = EventsPerPage / N, wxScrollBar:setScrollbar(ScrollBar, - trunc(EventPos * Share * PixelsPerEvent), + trunc(EventPos * Share * PixelsPerEvent), round(Share * Range), Range, round(Share * Range), diff --git a/lib/et/vsn.mk b/lib/et/vsn.mk index 9563a38000..d5416f1ea9 100644 --- a/lib/et/vsn.mk +++ b/lib/et/vsn.mk @@ -1 +1 @@ -ET_VSN = 1.6.3 +ET_VSN = 1.6.4 diff --git a/lib/ftp/doc/src/ftp.xml b/lib/ftp/doc/src/ftp.xml index 34e3ff84b0..ead2367925 100644 --- a/lib/ftp/doc/src/ftp.xml +++ b/lib/ftp/doc/src/ftp.xml @@ -29,7 +29,7 @@ <rev>B</rev> <file>ftp.xml</file> </header> - <module>ftp</module> + <module since="">ftp</module> <modulesummary>A File Transfer Protocol client.</modulesummary> <description> @@ -272,7 +272,7 @@ <funcs> <func> - <name>account(Pid, Account) -> ok | {error, Reason}</name> + <name since="">account(Pid, Account) -> ok | {error, Reason}</name> <fsummary>Specifies which account to use.</fsummary> <type> <v>Pid = pid()</v> @@ -289,8 +289,8 @@ </func> <func> - <name>append(Pid, LocalFile) -> </name> - <name>append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name> + <name since="">append(Pid, LocalFile) -> </name> + <name since="">append(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Transfers a file to remote server, and appends it to <c>Remotefile</c>.</fsummary> <type> @@ -310,7 +310,7 @@ </func> <func> - <name>append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name> + <name since="">append_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Transfers a binary into a remote file.</fsummary> <type> <v>Pid = pid()</v> @@ -328,7 +328,7 @@ </func> <func> - <name>append_chunk(Pid, Bin) -> ok | {error, Reason}</name> + <name since="">append_chunk(Pid, Bin) -> ok | {error, Reason}</name> <fsummary>Appends a chunk to the remote file.</fsummary> <type> <v>Pid = pid()</v> @@ -348,7 +348,7 @@ </func> <func> - <name>append_chunk_start(Pid, File) -> ok | {error, Reason}</name> + <name since="">append_chunk_start(Pid, File) -> ok | {error, Reason}</name> <fsummary>Starts transfer of file chunks for appending to <c>File</c>.</fsummary> <type> <v>Pid = pid()</v> @@ -365,7 +365,7 @@ </func> <func> - <name>append_chunk_end(Pid) -> ok | {error, Reason}</name> + <name since="">append_chunk_end(Pid) -> ok | {error, Reason}</name> <fsummary>Stops transfer of chunks for appending.</fsummary> <type> <v>Pid = pid()</v> @@ -381,7 +381,7 @@ </func> <func> - <name>cd(Pid, Dir) -> ok | {error, Reason}</name> + <name since="">cd(Pid, Dir) -> ok | {error, Reason}</name> <fsummary>Changes remote working directory.</fsummary> <type> <v>Pid = pid()</v> @@ -397,7 +397,7 @@ </func> <func> - <name>close(Pid) -> ok</name> + <name since="">close(Pid) -> ok</name> <fsummary>Ends the FTP session.</fsummary> <type> <v>Pid = pid()</v> @@ -411,7 +411,7 @@ </func> <func> - <name>delete(Pid, File) -> ok | {error, Reason}</name> + <name since="">delete(Pid, File) -> ok | {error, Reason}</name> <fsummary>Deletes a file at the remote server.</fsummary> <type> <v>Pid = pid()</v> @@ -426,7 +426,7 @@ </func> <func> - <name>formaterror(Tag) -> string()</name> + <name since="">formaterror(Tag) -> string()</name> <fsummary>Returns error diagnostics.</fsummary> <type> <v>Tag = {error, atom()} | atom()</v> @@ -440,7 +440,7 @@ </func> <func> - <name>lcd(Pid, Dir) -> ok | {error, Reason}</name> + <name since="">lcd(Pid, Dir) -> ok | {error, Reason}</name> <fsummary>Changes local working directory.</fsummary> <type> <v>Pid = pid()</v> @@ -455,7 +455,7 @@ </func> <func> - <name>lpwd(Pid) -> {ok, Dir}</name> + <name since="">lpwd(Pid) -> {ok, Dir}</name> <fsummary>Gets local current working directory.</fsummary> <type> <v>Pid = pid()</v> @@ -470,8 +470,8 @@ </func> <func> - <name>ls(Pid) -> </name> - <name>ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name> + <name since="">ls(Pid) -> </name> + <name since="">ls(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name> <fsummary>List of files.</fsummary> <type> <v>Pid = pid()</v> @@ -493,7 +493,7 @@ </func> <func> - <name>mkdir(Pid, Dir) -> ok | {error, Reason}</name> + <name since="">mkdir(Pid, Dir) -> ok | {error, Reason}</name> <fsummary>Creates a remote directory.</fsummary> <type> <v>Pid = pid()</v> @@ -510,8 +510,8 @@ </func> <func> - <name>nlist(Pid) -> </name> - <name>nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name> + <name since="">nlist(Pid) -> </name> + <name since="">nlist(Pid, Pathname) -> {ok, Listing} | {error, Reason}</name> <fsummary>List of files.</fsummary> <type> <v>Pid = pid()</v> @@ -535,8 +535,8 @@ </func> <func> - <name>open(Host) -> {ok, Pid} | {error, Reason}</name> - <name>open(Host, Opts) -> {ok, Pid} | {error, Reason}</name> + <name since="">open(Host) -> {ok, Pid} | {error, Reason}</name> + <name since="">open(Host, Opts) -> {ok, Pid} | {error, Reason}</name> <fsummary>Starts a standalone FTP client.</fsummary> <type> <v>Host = string() | ip_address()</v> @@ -587,7 +587,7 @@ </func> <func> - <name>pwd(Pid) -> {ok, Dir} | {error, Reason}</name> + <name since="">pwd(Pid) -> {ok, Dir} | {error, Reason}</name> <fsummary>Gets the remote current working directory.</fsummary> <type> <v>Pid = pid()</v> @@ -603,8 +603,8 @@ </func> <func> - <name>recv(Pid, RemoteFile) -> </name> - <name>recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name> + <name since="">recv(Pid, RemoteFile) -> </name> + <name since="">recv(Pid, RemoteFile, LocalFile) -> ok | {error, Reason}</name> <fsummary>Transfers a file from remote server.</fsummary> <type> <v>Pid = pid()</v> @@ -627,7 +627,7 @@ </func> <func> - <name>recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name> + <name since="">recv_bin(Pid, RemoteFile) -> {ok, Bin} | {error, Reason}</name> <fsummary>Transfers a file from remote server as a binary.</fsummary> <type> <v>Pid = pid()</v> @@ -644,7 +644,7 @@ </func> <func> - <name>recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name> + <name since="">recv_chunk_start(Pid, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Starts chunk-reading of the remote file.</fsummary> <type> <v>Pid = pid()</v> @@ -660,7 +660,7 @@ </func> <func> - <name>recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name> + <name since="">recv_chunk(Pid) -> ok | {ok, Bin} | {error, Reason}</name> <fsummary>Receives a chunk of the remote file.</fsummary> <type> <v>Pid = pid()</v> @@ -682,7 +682,7 @@ </func> <func> - <name>rename(Pid, Old, New) -> ok | {error, Reason}</name> + <name since="">rename(Pid, Old, New) -> ok | {error, Reason}</name> <fsummary>Renames a file at the remote server.</fsummary> <type> <v>Pid = pid()</v> @@ -697,7 +697,7 @@ </func> <func> - <name>rmdir(Pid, Dir) -> ok | {error, Reason}</name> + <name since="">rmdir(Pid, Dir) -> ok | {error, Reason}</name> <fsummary>Removes a remote directory.</fsummary> <type> <v>Pid = pid()</v> @@ -714,8 +714,8 @@ </func> <func> - <name>send(Pid, LocalFile) -></name> - <name>send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name> + <name since="">send(Pid, LocalFile) -></name> + <name since="">send(Pid, LocalFile, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Transfers a file to the remote server.</fsummary> <type> <v>Pid = pid()</v> @@ -732,7 +732,7 @@ </func> <func> - <name>send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name> + <name since="">send_bin(Pid, Bin, RemoteFile) -> ok | {error, Reason}</name> <fsummary>Transfers a binary into a remote file.</fsummary> <type> <v>Pid = pid()</v> @@ -749,7 +749,7 @@ </func> <func> - <name>send_chunk(Pid, Bin) -> ok | {error, Reason}</name> + <name since="">send_chunk(Pid, Bin) -> ok | {error, Reason}</name> <fsummary>Writes a chunk to the remote file.</fsummary> <type> <v>Pid = pid()</v> @@ -769,7 +769,7 @@ </func> <func> - <name>send_chunk_start(Pid, File) -> ok | {error, Reason}</name> + <name since="">send_chunk_start(Pid, File) -> ok | {error, Reason}</name> <fsummary>Starts transfer of file chunks.</fsummary> <type> <v>Pid = pid()</v> @@ -785,7 +785,7 @@ </func> <func> - <name>send_chunk_end(Pid) -> ok | {error, Reason}</name> + <name since="">send_chunk_end(Pid) -> ok | {error, Reason}</name> <fsummary>Stops transfer of chunks.</fsummary> <type> <v>Pid = pid()</v> @@ -801,7 +801,7 @@ </func> <func> - <name>start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name> + <name since="OTP 21.0">start_service(ServiceConfig) -> {ok, Pid} | {error, Reason}</name> <fsummary>Dynamically starts an <c>FTP</c> session after the <c>ftp</c> application has been started.</fsummary> <type> @@ -820,7 +820,7 @@ </func> <func> - <name>stop_service(Reference) -> ok | {error, Reason} </name> + <name since="OTP 21.0">stop_service(Reference) -> ok | {error, Reason} </name> <fsummary>Stops an FTP session.</fsummary> <type> <v>Reference = pid() | term() - service-specified reference</v> @@ -832,7 +832,7 @@ </func> <func> - <name>type(Pid, Type) -> ok | {error, Reason}</name> + <name since="">type(Pid, Type) -> ok | {error, Reason}</name> <fsummary>Sets transfer type to <c>ascii</c>or <c>binary</c>.</fsummary> <type> <v>Pid = pid()</v> @@ -849,7 +849,7 @@ </func> <func> - <name>user(Pid, User, Password) -> ok | {error, Reason}</name> + <name since="">user(Pid, User, Password) -> ok | {error, Reason}</name> <fsummary>User login.</fsummary> <type> <v>Pid = pid()</v> @@ -864,7 +864,7 @@ </func> <func> - <name>user(Pid, User, Password, Account) -> ok | {error, Reason}</name> + <name since="">user(Pid, User, Password, Account) -> ok | {error, Reason}</name> <fsummary>User login.</fsummary> <type> <v>Pid = pid()</v> @@ -880,7 +880,7 @@ </func> <func> - <name>quote(Pid, Command) -> [FTPLine]</name> + <name since="">quote(Pid, Command) -> [FTPLine]</name> <fsummary>Sends an arbitrary FTP command.</fsummary> <type> <v>Pid = pid()</v> diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml index d9f58382bc..e9cdf42018 100644 --- a/lib/hipe/doc/src/notes.xml +++ b/lib/hipe/doc/src/notes.xml @@ -31,6 +31,22 @@ </header> <p>This document describes the changes made to HiPE.</p> +<section><title>Hipe 3.18.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>The code was updated to avoid causing a dialyzer + warning because of a tightened spec for + <c>beam_lib:info/1</c>.</p> + <p> + Own Id: OTP-15482</p> + </item> + </list> + </section> + +</section> + <section><title>Hipe 3.18.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk index b51f17aff0..12d621bf01 100644 --- a/lib/hipe/vsn.mk +++ b/lib/hipe/vsn.mk @@ -1 +1 @@ -HIPE_VSN = 3.18.1 +HIPE_VSN = 3.18.2 diff --git a/lib/inets/doc/src/http_uri.xml b/lib/inets/doc/src/http_uri.xml index 2dec5acbf9..6d3547f4fe 100644 --- a/lib/inets/doc/src/http_uri.xml +++ b/lib/inets/doc/src/http_uri.xml @@ -30,7 +30,7 @@ <rev></rev> </header> - <module>http_uri</module> + <module since="OTP R15B01">http_uri</module> <modulesummary>URI utility module</modulesummary> <description> @@ -79,7 +79,7 @@ <funcs> <func> - <name>decode(HexEncodedURI) -> URI</name> + <name since="OTP R15B01">decode(HexEncodedURI) -> URI</name> <fsummary>Decodes a hexadecimal encoded URI.</fsummary> <type> @@ -93,7 +93,7 @@ </desc> </func> <func> - <name>encode(URI) -> HexEncodedURI</name> + <name since="OTP R15B01">encode(URI) -> HexEncodedURI</name> <fsummary>Encodes a hexadecimal encoded URI.</fsummary> <type> @@ -109,8 +109,8 @@ </func> <func> - <name>parse(URI) -> {ok, Result} | {error, Reason}</name> - <name>parse(URI, Options) -> {ok, Result} | {error, Reason}</name> + <name since="OTP R15B01">parse(URI) -> {ok, Result} | {error, Reason}</name> + <name since="OTP R15B01">parse(URI, Options) -> {ok, Result} | {error, Reason}</name> <fsummary>Parses a URI.</fsummary> <type> <v>URI = uri()</v> @@ -165,7 +165,7 @@ fun(SchemeStr :: string() | binary()) -> </func> <func> - <name>scheme_defaults() -> SchemeDefaults</name> + <name since="OTP R15B01">scheme_defaults() -> SchemeDefaults</name> <fsummary>A list of the scheme and their default ports.</fsummary> <type> <v>SchemeDefaults = [{scheme(), default_scheme_port_number()}] </v> diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index a2871f3b95..7451b314ec 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -30,7 +30,7 @@ <rev></rev> </header> - <module>httpc</module> + <module since="OTP R13B04">httpc</module> <modulesummary>An HTTP/1.1 client</modulesummary> <description> @@ -151,8 +151,8 @@ <funcs> <func> - <name>cancel_request(RequestId) -></name> - <name>cancel_request(RequestId, Profile) -> ok</name> + <name since="OTP R13B04">cancel_request(RequestId) -></name> + <name since="OTP R13B04">cancel_request(RequestId, Profile) -> ok</name> <fsummary>Cancels an asynchronous HTTP request.</fsummary> <type> <v>RequestId = request_id() - A unique identifier as returned @@ -169,9 +169,9 @@ </func> <func> - <name>cookie_header(Url) -> </name> - <name>cookie_header(Url, Profile | Opts) -> header() | {error, Reason}</name> - <name>cookie_header(Url, Opts, Profile) -> header() | {error, Reason}</name> + <name since="OTP R13B04">cookie_header(Url) -> </name> + <name since="OTP R13B04">cookie_header(Url, Profile | Opts) -> header() | {error, Reason}</name> + <name since="OTP R15B">cookie_header(Url, Opts, Profile) -> header() | {error, Reason}</name> <fsummary>Returns the cookie header that would have been sent when making a request to URL using the profile <c>Profile</c>.</fsummary> <type> @@ -193,8 +193,8 @@ </func> <func> - <name>get_options(OptionItems) -> {ok, Values} | {error, Reason}</name> - <name>get_options(OptionItems, Profile) -> {ok, Values} | {error, Reason}</name> + <name since="OTP R15B01">get_options(OptionItems) -> {ok, Values} | {error, Reason}</name> + <name since="OTP R15B01">get_options(OptionItems, Profile) -> {ok, Values} | {error, Reason}</name> <fsummary>Gets the currently used options.</fsummary> <type> <v>OptionItems = all | [option_item()]</v> @@ -223,8 +223,8 @@ </func> <func> - <name>info() -> list()</name> - <name>info(Profile) -> list()</name> + <name since="OTP R15B02">info() -> list()</name> + <name since="OTP R15B02">info(Profile) -> list()</name> <fsummary>Produces a list of miscellaneous information.</fsummary> <type> <v>Profile = profile() | pid()</v> @@ -239,8 +239,8 @@ <func> - <name>reset_cookies() -> void()</name> - <name>reset_cookies(Profile) -> void()</name> + <name since="OTP R13B04">reset_cookies() -> void()</name> + <name since="OTP R13B04">reset_cookies(Profile) -> void()</name> <fsummary>Resets the cookie database.</fsummary> <type> <v>Profile = profile() | pid()</v> @@ -254,8 +254,8 @@ </func> <func> - <name>request(Url) -> </name> - <name>request(Url, Profile) -> {ok, Result} | {error, Reason}</name> + <name since="OTP R13B04">request(Url) -> </name> + <name since="OTP R13B04">request(Url, Profile) -> {ok, Result} | {error, Reason}</name> <fsummary>Sends a get HTTP request.</fsummary> <type> <v>Url = url()</v> @@ -272,8 +272,8 @@ </func> <func> - <name>request(Method, Request, HTTPOptions, Options) -></name> - <name>request(Method, Request, HTTPOptions, Options, Profile) -> {ok, Result} | {ok, saved_to_file} | {error, Reason}</name> + <name since="OTP R13B04">request(Method, Request, HTTPOptions, Options) -></name> + <name since="OTP R13B04">request(Method, Request, HTTPOptions, Options, Profile) -> {ok, Result} | {ok, saved_to_file} | {error, Reason}</name> <fsummary>Sends an HTTP request.</fsummary> <type> @@ -521,8 +521,8 @@ <func> - <name>set_options(Options) -> </name> - <name>set_options(Options, Profile) -> ok | {error, Reason}</name> + <name since="OTP R13B04">set_options(Options) -> </name> + <name since="OTP R13B04">set_options(Options, Profile) -> ok | {error, Reason}</name> <fsummary>Sets options to be used for subsequent requests.</fsummary> <type> <v>Options = [Option]</v> @@ -639,8 +639,8 @@ </func> <func> - <name>store_cookies(SetCookieHeaders, Url) -> </name> - <name>store_cookies(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name> + <name since="OTP R14B02">store_cookies(SetCookieHeaders, Url) -> </name> + <name since="OTP R14B02">store_cookies(SetCookieHeaders, Url, Profile) -> ok | {error, Reason}</name> <fsummary>Saves the cookies defined in <c>SetCookieHeaders</c> in the client profile cookie database.</fsummary> <type> @@ -658,7 +658,7 @@ </func> <func> - <name>stream_next(Pid) -> ok</name> + <name since="OTP R13B04">stream_next(Pid) -> ok</name> <fsummary>Triggers the next message to be streamed, that is, the same behavior as active one for sockets. </fsummary> @@ -676,8 +676,8 @@ </func> <func> - <name>which_cookies() -> cookies()</name> - <name>which_cookies(Profile) -> cookies()</name> + <name since="OTP R13B04">which_cookies() -> cookies()</name> + <name since="OTP R13B04">which_cookies(Profile) -> cookies()</name> <fsummary>Dumps the entire cookie database.</fsummary> <type> <v>Profile = profile() | pid()</v> @@ -695,8 +695,8 @@ </func> <func> - <name>which_sessions() -> session_info()</name> - <name>which_sessions(Profile) -> session_info()</name> + <name since="OTP R15B02">which_sessions() -> session_info()</name> + <name since="OTP R15B02">which_sessions(Profile) -> session_info()</name> <fsummary>Produces a slightly processed dump of the sessions database.</fsummary> <type> <v>Profile = profile() | pid()</v> diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index e5adb4eb20..1750078db0 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -29,7 +29,7 @@ <rev>2.2</rev> <file>httpd.sgml</file> </header> - <module>httpd</module> + <module since="">httpd</module> <modulesummary> HTTP server API </modulesummary> @@ -874,8 +874,8 @@ text/plain asc txt</pre> <funcs> <func> - <name>info(Pid) -></name> - <name>info(Pid, Properties) -> [{Option, Value}]</name> + <name since="">info(Pid) -></name> + <name since="">info(Pid, Properties) -> [{Option, Value}]</name> <fsummary>Fetches information about the HTTP server.</fsummary> <type> <v>Properties = [property()]</v> @@ -899,10 +899,10 @@ text/plain asc txt</pre> </func> <func> - <name>info(Address, Port) -> </name> - <name>info(Address, Port, Profile) -> </name> - <name>info(Address, Port, Profile, Properties) -> [{Option, Value}] </name> - <name>info(Address, Port, Properties) -> [{Option, Value}] </name> + <name since="">info(Address, Port) -> </name> + <name since="">info(Address, Port, Profile) -> </name> + <name since="OTP 18.0">info(Address, Port, Profile, Properties) -> [{Option, Value}] </name> + <name since="">info(Address, Port, Properties) -> [{Option, Value}] </name> <fsummary>Fetches information about the HTTP server.</fsummary> <type> <v>Address = ip_address()</v> @@ -927,7 +927,7 @@ text/plain asc txt</pre> </func> <func> - <name>reload_config(Config, Mode) -> ok | {error, Reason}</name> + <name since="">reload_config(Config, Mode) -> ok | {error, Reason}</name> <fsummary>Reloads the HTTP server configuration without restarting the server.</fsummary> <type> @@ -1051,7 +1051,7 @@ text/plain asc txt</pre> </section> <funcs> <func> - <name>Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name> + <name since="">Module:do(ModData)-> {proceed, OldData} | {proceed, NewData} | {break, NewData} | done</name> <fsummary>Called for each request to the web server.</fsummary> <type> <v>OldData = list()</v> @@ -1105,7 +1105,7 @@ text/plain asc txt</pre> </func> <func> - <name>Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name> + <name since="">Module:load(Line, AccIn)-> eof | ok | {ok, AccOut} | {ok, AccOut, {Option, Value}} | {ok, AccOut, [{Option, Value}]} | {error, Reason}</name> <fsummary>Converts a line in an Apache-like config file to an <c>{Option, Value}</c> tuple.</fsummary> <type> @@ -1128,7 +1128,7 @@ text/plain asc txt</pre> </func> <func> - <name>Module:remove(ConfigDB) -> ok | {error, Reason} </name> + <name since="">Module:remove(ConfigDB) -> ok | {error, Reason} </name> <fsummary>Callback function that is called when the web server is closed.</fsummary> <type> <v>ConfigDB = ets_table()</v> @@ -1143,7 +1143,7 @@ text/plain asc txt</pre> </func> <func> - <name>Module:store({Option, Value}, Config)-> {ok, {Option, NewValue}} | {error, Reason}</name> + <name since="">Module:store({Option, Value}, Config)-> {ok, {Option, NewValue}} | {error, Reason}</name> <fsummary>Checks the validity of the configuration options.</fsummary> <type> <v>Line = string()</v> @@ -1171,7 +1171,7 @@ text/plain asc txt</pre> </section> <funcs> <func> - <name>parse_query(QueryString) -> [{Key,Value}]</name> + <name since="">parse_query(QueryString) -> [{Key,Value}]</name> <fsummary>Parses incoming data to <c>erl</c> and <c>eval</c> scripts.</fsummary> <type> <v>QueryString = string()</v> diff --git a/lib/inets/doc/src/httpd_custom_api.xml b/lib/inets/doc/src/httpd_custom_api.xml index d2e5441895..2c0f92ff83 100644 --- a/lib/inets/doc/src/httpd_custom_api.xml +++ b/lib/inets/doc/src/httpd_custom_api.xml @@ -25,7 +25,7 @@ <title>httpd_custom_api</title> <file>httpd_custom_api.xml</file> </header> - <module>httpd_custom_api</module> + <module since="OTP 17.5.6">httpd_custom_api</module> <modulesummary>Behaviour with optional callbacks to customize the inets HTTP server.</modulesummary> <description> <p> The module implementing this behaviour shall be supplied to to the servers @@ -34,7 +34,7 @@ </description> <funcs> <func> - <name>response_default_headers() -> [Header] </name> + <name since="OTP 18.1.1">response_default_headers() -> [Header] </name> <fsummary>Provide default headers for the HTTP servers responses.</fsummary> <type> <v>Header = {HeaderName :: string(), HeaderValue::string()}</v> @@ -48,7 +48,7 @@ </func> <func> - <name>response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> + <name since="OTP 17.5.6">response_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> <fsummary>Filter and possible alter HTTP response headers.</fsummary> <type> <v>Header = {HeaderName :: string(), HeaderValue::string()}</v> @@ -61,7 +61,7 @@ </func> <func> - <name>request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> + <name since="OTP 17.5.6">request_header({HeaderName, HeaderValue}) -> {true, Header} | false </name> <fsummary>Filter and possible alter HTTP request headers.</fsummary> <type> <v>Header = {HeaderName :: string(), HeaderValue::string()}</v> diff --git a/lib/inets/doc/src/httpd_socket.xml b/lib/inets/doc/src/httpd_socket.xml index d3aa82a540..22ead06f38 100644 --- a/lib/inets/doc/src/httpd_socket.xml +++ b/lib/inets/doc/src/httpd_socket.xml @@ -29,7 +29,7 @@ <rev>2.2</rev> <file>httpd_socket.sgml</file> </header> - <module>httpd_socket</module> + <module since="">httpd_socket</module> <modulesummary>Communication utility functions to be used by the Erlang web server API programmer.</modulesummary> <description> @@ -43,7 +43,7 @@ <funcs> <func> - <name>deliver(SocketType, Socket, Data) -> Result</name> + <name since="">deliver(SocketType, Socket, Data) -> Result</name> <fsummary>Sends binary data over socket.</fsummary> <type> <v>SocketType = socket_type()</v> @@ -63,7 +63,7 @@ </func> <func> - <name>peername(SocketType,Socket) -> {Port,IPAddress}</name> + <name since="">peername(SocketType,Socket) -> {Port,IPAddress}</name> <fsummary>Returns the port and IP address of the remote socket.</fsummary> <type> <v>SocketType = socket_type()</v> @@ -81,7 +81,7 @@ </func> <func> - <name>resolve() -> HostName</name> + <name since="">resolve() -> HostName</name> <fsummary>Returns the official name of the current host.</fsummary> <type> <v>HostName = string()</v> diff --git a/lib/inets/doc/src/httpd_util.xml b/lib/inets/doc/src/httpd_util.xml index 220a2ede35..e0f947f860 100644 --- a/lib/inets/doc/src/httpd_util.xml +++ b/lib/inets/doc/src/httpd_util.xml @@ -29,7 +29,7 @@ <rev>2.2</rev> <file>httpd_util.sgml</file> </header> - <module>httpd_util</module> + <module since="">httpd_util</module> <modulesummary>Miscellaneous utility functions to be used when implementing Erlang web server API modules.</modulesummary> <description> @@ -41,12 +41,11 @@ <funcs> <func> - <name>convert_request_date(DateString) -> ErlDate|bad_date</name> + <name since="">convert_request_date(DateString) -> ErlDate|bad_date</name> <fsummary>Converts the date to the Erlang date format.</fsummary> <type> <v>DateString = string()</v> - <v>ErlDate = {{Year,Month,Date},{Hour,Min,Sec}}</v> - <v>Year = Month = Date = Hour = Min = Sec = integer()</v> + <v>ErlDate = calendar:datetime() </v> </type> <desc> <p><c>convert_request_date/1</c> converts <c>DateString</c> to @@ -57,7 +56,7 @@ </func> <func> - <name>create_etag(FileInfo) -> Etag</name> + <name since="">create_etag(FileInfo) -> Etag</name> <fsummary>Calculates the Etag for a file.</fsummary> <type> <v>FileInfo = file_info()</v> @@ -71,7 +70,7 @@ </func> <func> - <name>day(NthDayOfWeek) -> DayOfWeek</name> + <name since="">day(NthDayOfWeek) -> DayOfWeek</name> <fsummary>Converts the day of the week (integer [1-7]) to an abbreviated string.</fsummary> <type> @@ -87,7 +86,7 @@ </func> <func> - <name>decode_hex(HexValue) -> DecValue</name> + <name since="">decode_hex(HexValue) -> DecValue</name> <fsummary>Converts a hexadecimal value into its decimal equivalent.</fsummary> <type> <v>HexValue = DecValue = string()</v> @@ -99,7 +98,7 @@ </func> <func> - <name>flatlength(NestedList) -> Size</name> + <name since="">flatlength(NestedList) -> Size</name> <fsummary>Computes the size of a possibly nested list.</fsummary> <type> <v>NestedList = list()</v> @@ -112,7 +111,7 @@ </func> <func> - <name>hexlist_to_integer(HexString) -> Number</name> + <name since="">hexlist_to_integer(HexString) -> Number</name> <fsummary>Converts a hexadecimal string to an integer.</fsummary> <type> <v>Number = integer()</v> @@ -125,7 +124,7 @@ </func> <func> - <name>integer_to_hexlist(Number) -> HexString</name> + <name since="">integer_to_hexlist(Number) -> HexString</name> <fsummary>Converts an integer to a hexadecimal string.</fsummary> <type> <v>Number = integer()</v> @@ -138,8 +137,8 @@ </func> <func> - <name>lookup(ETSTable,Key) -> Result</name> - <name>lookup(ETSTable,Key,Undefined) -> Result</name> + <name since="">lookup(ETSTable,Key) -> Result</name> + <name since="">lookup(ETSTable,Key,Undefined) -> Result</name> <fsummary>Extracts the first value associated with a <c>Key</c> in an ETS table.</fsummary> <type> @@ -160,8 +159,8 @@ </func> <func> - <name>lookup_mime(ConfigDB,Suffix)</name> - <name>lookup_mime(ConfigDB,Suffix,Undefined) -> MimeType</name> + <name since="">lookup_mime(ConfigDB,Suffix)</name> + <name since="">lookup_mime(ConfigDB,Suffix,Undefined) -> MimeType</name> <fsummary>Returns the MIME type associated with a specific file suffix.</fsummary> <type> <v>ConfigDB = ets_table()</v> @@ -179,8 +178,8 @@ </func> <func> - <name>lookup_mime_default(ConfigDB,Suffix)</name> - <name>lookup_mime_default(ConfigDB,Suffix,Undefined) -> MimeType</name> + <name since="">lookup_mime_default(ConfigDB,Suffix)</name> + <name since="">lookup_mime_default(ConfigDB,Suffix,Undefined) -> MimeType</name> <fsummary>Returns the MIME type associated with a specific file suffix or the value of the DefaultType.</fsummary> <type> @@ -201,7 +200,7 @@ </func> <func> - <name>message(StatusCode,PhraseArgs,ConfigDB) -> Message</name> + <name since="">message(StatusCode,PhraseArgs,ConfigDB) -> Message</name> <fsummary>Returns an informative HTTP 1.1 status string in HTML.</fsummary> <type> <v>StatusCode = 301 | 400 | 403 | 404 | 500 | 501 | 504</v> @@ -236,7 +235,7 @@ </func> <func> - <name>month(NthMonth) -> Month</name> + <name since="">month(NthMonth) -> Month</name> <fsummary>Converts the month as an integer (1-12) to an abbreviated string.</fsummary> <type> <v>NthMonth = 1-12</v> @@ -250,7 +249,7 @@ </func> <func> - <name>multi_lookup(ETSTable,Key) -> Result</name> + <name since="">multi_lookup(ETSTable,Key) -> Result</name> <fsummary>Extracts the values associated with a key in an ETS table.</fsummary> <type> <v>ETSTable = ets_table()</v> @@ -265,7 +264,7 @@ </func> <func> - <name>reason_phrase(StatusCode) -> Description</name> + <name since="">reason_phrase(StatusCode) -> Description</name> <fsummary>Returns the description of an HTTP 1.1 status code.</fsummary> <type> <v>StatusCode = 100| 200 | 201 | 202 | 204 | 205 | 206 | 300 | 301 | 302 | 303 | 304 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 410 411 | 412 | 413 | 414 415 | 416 | 417 | 500 | 501 | 502 | 503 | 504 | 505</v> @@ -280,11 +279,11 @@ </func> <func> - <name>rfc1123_date() -> RFC1123Date</name> - <name>rfc1123_date({{YYYY,MM,DD},{Hour,Min,Sec}}) -> RFC1123Date</name> + <name since="">rfc1123_date() -> RFC1123Date</name> + <name since="">rfc1123_date(Date) -> RFC1123Date</name> <fsummary>Returns the current date in RFC 1123 format.</fsummary> <type> - <v>YYYY = MM = DD = Hour = Min = Sec = integer()</v> + <v> Date = calendar:datetime()</v> <v>RFC1123Date = string()</v> </type> <desc> @@ -295,7 +294,7 @@ </func> <func> - <name>split(String,RegExp,N) -> SplitRes</name> + <name since="">split(String,RegExp,N) -> SplitRes</name> <fsummary>Splits a string in N chunks using a regular expression.</fsummary> <type> <v>String = RegExp = string()</v> @@ -313,7 +312,7 @@ </func> <func> - <name>split_script_path(RequestLine) -> Splitted</name> + <name since="">split_script_path(RequestLine) -> Splitted</name> <fsummary>Splits a <c>RequestLine</c> in a file reference to an executable, and a <c>QueryString</c> or a <c>PathInfo</c>string.</fsummary> <type> @@ -330,7 +329,7 @@ </func> <func> - <name>split_path(RequestLine) -> {Path,QueryStringOrPathInfo}</name> + <name since="">split_path(RequestLine) -> {Path,QueryStringOrPathInfo}</name> <fsummary>Splits a <c>RequestLine</c> in a file reference, and a <c>QueryString</c> or a <c>PathInfo</c> string.</fsummary> <type> @@ -356,7 +355,7 @@ </func> <func> - <name>strip(String) -> Stripped</name> + <name since="">strip(String) -> Stripped</name> <fsummary>Returns <c>String</c> where the leading and trailing space tabs are removed.</fsummary> <type> @@ -370,7 +369,7 @@ </func> <func> - <name>suffix(FileName) -> Suffix</name> + <name since="">suffix(FileName) -> Suffix</name> <fsummary>Extracts the file suffix from a given filename.</fsummary> <type> <v>FileName = Suffix = string()</v> diff --git a/lib/inets/doc/src/inets.xml b/lib/inets/doc/src/inets.xml index 9b0ffaad5e..176af3137a 100644 --- a/lib/inets/doc/src/inets.xml +++ b/lib/inets/doc/src/inets.xml @@ -29,7 +29,7 @@ <date></date> <rev></rev> </header> - <module>inets</module> + <module since="">inets</module> <modulesummary>The Inets services API.</modulesummary> <description> <p>This module provides the most basic API to the @@ -51,7 +51,7 @@ <funcs> <func> - <name>services() -> [{Service, Pid}]</name> + <name since="">services() -> [{Service, Pid}]</name> <fsummary>Returns a list of currently running services.</fsummary> <type> <v>Service = service()</v> @@ -68,7 +68,7 @@ </func> <func> - <name>services_info() -> [{Service, Pid, Info}]</name> + <name since="">services_info() -> [{Service, Pid, Info}]</name> <fsummary>Returns a list of currently running services where each service is described by an <c>[{Option, Value}]</c> list.</fsummary> @@ -91,7 +91,7 @@ </func> <func> - <name>service_names() -> [Service] </name> + <name since="">service_names() -> [Service] </name> <fsummary>Returns a list of available service names.</fsummary> <type> <v>Service = service()</v> @@ -104,8 +104,8 @@ </func> <func> - <name>start() -> </name> - <name>start(Type) -> ok | {error, Reason}</name> + <name since="">start() -> </name> + <name since="">start(Type) -> ok | {error, Reason}</name> <fsummary>Starts the <c>Inets</c> application.</fsummary> <type> <v>Type = permanent | transient | temporary</v> @@ -120,8 +120,8 @@ </func> <func> - <name>start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}</name> - <name>start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}</name> + <name since="">start(Service, ServiceConfig) -> {ok, Pid} | {error, Reason}</name> + <name since="">start(Service, ServiceConfig, How) -> {ok, Pid} | {error, Reason}</name> <fsummary>Dynamically starts an <c>Inets</c> service after the <c>Inets</c> application has been started.</fsummary> <type> @@ -156,7 +156,7 @@ </func> <func> - <name>stop() -> ok </name> + <name since="">stop() -> ok </name> <fsummary>Stops the <c>Inets</c> application.</fsummary> <desc> <p>Stops the <c>Inets</c> application. See also @@ -167,7 +167,7 @@ </func> <func> - <name>stop(Service, Reference) -> ok | {error, Reason} </name> + <name since="">stop(Service, Reference) -> ok | {error, Reason} </name> <fsummary>Stops a started service of the <c>Inets</c> application or takes down a <c>stand_alone </c>service gracefully.</fsummary> <type> diff --git a/lib/inets/doc/src/mod_alias.xml b/lib/inets/doc/src/mod_alias.xml index 6ae19700a5..ff57d49d08 100644 --- a/lib/inets/doc/src/mod_alias.xml +++ b/lib/inets/doc/src/mod_alias.xml @@ -29,7 +29,7 @@ <rev>2.2</rev> <file>mod_alias.sgml</file> </header> - <module>mod_alias</module> + <module since="">mod_alias</module> <modulesummary>URL aliasing.</modulesummary> <description> <p>Erlang web server internal API for handling of, for example, @@ -40,7 +40,7 @@ <funcs> <func> - <name>default_index(ConfigDB, Path) -> NewPath</name> + <name since="">default_index(ConfigDB, Path) -> NewPath</name> <fsummary>Returns a new path with the default resource or file appended.</fsummary> <type> <v>ConfigDB = config_db()</v> @@ -64,7 +64,7 @@ </func> <func> - <name>path(PathData, ConfigDB, RequestURI) -> Path</name> + <name since="">path(PathData, ConfigDB, RequestURI) -> Path</name> <fsummary>Returns the file path to a URL.</fsummary> <type> <v>PathData = interaction_data()</v> @@ -89,7 +89,7 @@ </func> <func> - <name>real_name(ConfigDB, RequestURI, Aliases) -> Ret</name> + <name since="">real_name(ConfigDB, RequestURI, Aliases) -> Ret</name> <fsummary>Expands a request URI using <c>Aliases</c> config directives.</fsummary> <type> <v>ConfigDB = config_db()</v> @@ -120,7 +120,7 @@ </func> <func> - <name>real_script_name(ConfigDB, RequestURI, ScriptAliases) -> Ret</name> + <name since="">real_script_name(ConfigDB, RequestURI, ScriptAliases) -> Ret</name> <fsummary>Expands a request URI using <c>ScriptAliases</c> config directives.</fsummary> <type> diff --git a/lib/inets/doc/src/mod_auth.xml b/lib/inets/doc/src/mod_auth.xml index c4f844622b..ad864ca4d1 100644 --- a/lib/inets/doc/src/mod_auth.xml +++ b/lib/inets/doc/src/mod_auth.xml @@ -29,7 +29,7 @@ <rev>2.3</rev> <file>mod_auth.sgml</file> </header> - <module>mod_auth</module> + <module since="">mod_auth</module> <modulesummary>User authentication using text files, Dets, or Mnesia database.</modulesummary> <description> <p>This module provides for basic user authentication using @@ -38,9 +38,9 @@ <funcs> <func> - <name>add_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name> - <name>add_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name> - <name>add_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name> + <name since="">add_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name> + <name since="">add_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name> + <name since="">add_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name> <fsummary>Adds a user to a group.</fsummary> <type> <v>GroupName = string()</v> @@ -65,9 +65,9 @@ </func> <func> - <name>add_user(UserName, Options) -> true| {error, Reason}</name> - <name>add_user(UserName, Password, UserData, Port, Dir) -> true | {error, Reason}</name> - <name>add_user(UserName, Password, UserData, Address, Port, Dir) -> true | {error, Reason}</name> + <name since="">add_user(UserName, Options) -> true| {error, Reason}</name> + <name since="">add_user(UserName, Password, UserData, Port, Dir) -> true | {error, Reason}</name> + <name since="">add_user(UserName, Password, UserData, Address, Port, Dir) -> true | {error, Reason}</name> <fsummary>Adds a user to the user database.</fsummary> <type> <v>UserName = string()</v> @@ -92,8 +92,8 @@ </func> <func> - <name>delete_group(GroupName, Options) -> true | {error,Reason} <name>delete_group(GroupName, Port, Dir) -> true | {error, Reason}</name> - <name>delete_group(GroupName, Address, Port, Dir) -> true | {error, Reason}</name> + <name since="">delete_group(GroupName, Options) -> true | {error,Reason} <name>delete_group(GroupName, Port, Dir) -> true | {error, Reason}</name> + <name since="">delete_group(GroupName, Address, Port, Dir) -> true | {error, Reason}</name> <fsummary>Deletes a group.</fsummary> <type> <v>Options = [Option]</v> @@ -115,9 +115,9 @@ </func> <func> - <name>delete_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name> - <name>delete_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name> - <name>delete_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name> + <name since="">delete_group_member(GroupName, UserName, Options) -> true | {error, Reason}</name> + <name since="">delete_group_member(GroupName, UserName, Port, Dir) -> true | {error, Reason}</name> + <name since="">delete_group_member(GroupName, UserName, Address, Port, Dir) -> true | {error, Reason}</name> <fsummary>Removes a user from a group.</fsummary> <type> <v>GroupName = string()</v> @@ -141,9 +141,9 @@ </func> <func> - <name>delete_user(UserName,Options) -> true | {error, Reason}</name> - <name>delete_user(UserName, Port, Dir) -> true | {error, Reason}</name> - <name>delete_user(UserName, Address, Port, Dir) -> true | {error, Reason}</name> + <name since="">delete_user(UserName,Options) -> true | {error, Reason}</name> + <name since="">delete_user(UserName, Port, Dir) -> true | {error, Reason}</name> + <name since="">delete_user(UserName, Address, Port, Dir) -> true | {error, Reason}</name> <fsummary>Deletes a user from the user database.</fsummary> <type> <v>UserName = string()</v> @@ -166,9 +166,9 @@ </func> <func> - <name>get_user(UserName,Options) -> {ok, #httpd_user} |{error, Reason}</name> - <name>get_user(UserName, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name> - <name>get_user(UserName, Address, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name> + <name since="">get_user(UserName,Options) -> {ok, #httpd_user} |{error, Reason}</name> + <name since="">get_user(UserName, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name> + <name since="">get_user(UserName, Address, Port, Dir) -> {ok, #httpd_user} | {error, Reason}</name> <fsummary>Returns a user from the user database.</fsummary> <type> <v>UserName = string()</v> @@ -190,9 +190,9 @@ </func> <func> - <name>list_groups(Options) -> {ok, Groups} | {error, Reason}</name> - <name>list_groups(Port, Dir) -> {ok, Groups} | {error, Reason}</name> - <name>list_groups(Address, Port, Dir) -> {ok, Groups} | {error, Reason}</name> + <name since="">list_groups(Options) -> {ok, Groups} | {error, Reason}</name> + <name since="">list_groups(Port, Dir) -> {ok, Groups} | {error, Reason}</name> + <name since="">list_groups(Address, Port, Dir) -> {ok, Groups} | {error, Reason}</name> <fsummary>Lists all the groups.</fsummary> <type> <v>Options = [Option]</v> @@ -214,9 +214,9 @@ </func> <func> - <name>list_group_members(GroupName, Options) -> {ok, Users} | {error, Reason}</name> - <name>list_group_members(GroupName, Port, Dir) -> {ok, Users} | {error, Reason}</name> - <name>list_group_members(GroupName, Address, Port, Dir) -> {ok, Users} | {error, Reason}</name> + <name since="">list_group_members(GroupName, Options) -> {ok, Users} | {error, Reason}</name> + <name since="">list_group_members(GroupName, Port, Dir) -> {ok, Users} | {error, Reason}</name> + <name since="">list_group_members(GroupName, Address, Port, Dir) -> {ok, Users} | {error, Reason}</name> <fsummary>Lists the members of a group.</fsummary> <type> <v>GroupName = string()</v> @@ -240,9 +240,9 @@ </func> <func> - <name>list_users(Options) -> {ok, Users} | {error, Reason}</name> - <name>list_users(Port, Dir) -> {ok, Users} | {error, Reason}</name> - <name>list_users(Address, Port, Dir) -> {ok, Users} | {error, Reason}</name> + <name since="">list_users(Options) -> {ok, Users} | {error, Reason}</name> + <name since="OTP R14B01">list_users(Port, Dir) -> {ok, Users} | {error, Reason}</name> + <name since="">list_users(Address, Port, Dir) -> {ok, Users} | {error, Reason}</name> <fsummary>Lists users in the user database.</fsummary> <type> <v>Options = [Option]</v> @@ -264,8 +264,8 @@ </func> <func> - <name>update_password(Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name> - <name>update_password(Address,Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name> + <name since="">update_password(Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name> + <name since="">update_password(Address,Port, Dir, OldPassword, NewPassword, NewPassword) -> ok | {error, Reason}</name> <fsummary>Changes <c>AuthAcessPassword</c>.</fsummary> <type> <v>Port = integer()</v> diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml index ede7dc8f7d..bc5f98068f 100644 --- a/lib/inets/doc/src/mod_esi.xml +++ b/lib/inets/doc/src/mod_esi.xml @@ -25,7 +25,7 @@ <title>mod_esi</title> <file>mod_esi.sgml</file> </header> - <module>mod_esi</module> + <module since="">mod_esi</module> <modulesummary>Erlang Server Interface</modulesummary> <description> <p>This module defines the Erlang Server Interface (ESI) API. @@ -88,7 +88,7 @@ <funcs> <func> - <name>deliver(SessionID, Data) -> ok | {error, Reason}</name> + <name since="">deliver(SessionID, Data) -> ok | {error, Reason}</name> <fsummary>Sends <c>Data</c> back to client.</fsummary> <type> <v>SessionID = term()</v> @@ -121,7 +121,7 @@ <funcs> <func> - <name>Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name> + <name since="">Module:Function(SessionID, Env, Input)-> {continue, State} | _ </name> <fsummary>Creates a dynamic web page and returns it chunk by chunk to the server process by calling <c>mod_esi:deliver/2</c>.</fsummary> <type> @@ -179,7 +179,7 @@ </func> <func> - <name>Module:Function(Env, Input)-> Response </name> + <name since="">Module:Function(Env, Input)-> Response </name> <fsummary>Creates a dynamic web page and returns it as a list. This function is deprecated and is only kept for backwards compatibility.</fsummary> <type> diff --git a/lib/inets/doc/src/mod_security.xml b/lib/inets/doc/src/mod_security.xml index 6f3f3c048a..c26d7468c2 100644 --- a/lib/inets/doc/src/mod_security.xml +++ b/lib/inets/doc/src/mod_security.xml @@ -29,7 +29,7 @@ <rev>1.0</rev> <file>mod_security.sgml</file> </header> - <module>mod_security</module> + <module since="">mod_security</module> <modulesummary>Security Audit and Trailing Functionality</modulesummary> <description> <p>Security Audit and Trailing Functionality</p> @@ -37,8 +37,8 @@ <funcs> <func> - <name>block_user(User, Port, Dir, Seconds) -> true | {error, Reason}</name> - <name>block_user(User, Address, Port, Dir, Seconds) -> true | {error, Reason}</name> + <name since="">block_user(User, Port, Dir, Seconds) -> true | {error, Reason}</name> + <name since="">block_user(User, Address, Port, Dir, Seconds) -> true | {error, Reason}</name> <fsummary>Blocks a user from access to a directory for a certain amount of time.</fsummary> <type> <v>User = string()</v> @@ -56,10 +56,10 @@ </func> <func> - <name>list_auth_users(Port) -> Users | []</name> - <name>list_auth_users(Address, Port) -> Users | []</name> - <name>list_auth_users(Port, Dir) -> Users | []</name> - <name>list_auth_users(Address, Port, Dir) -> Users | []</name> + <name since="">list_auth_users(Port) -> Users | []</name> + <name since="">list_auth_users(Address, Port) -> Users | []</name> + <name since="">list_auth_users(Port, Dir) -> Users | []</name> + <name since="">list_auth_users(Address, Port, Dir) -> Users | []</name> <fsummary>Lists users that have authenticated within the <c>SecurityAuthTimeout</c> time for a given address (if specified), port number, and directory (if specified).</fsummary> @@ -77,10 +77,10 @@ </desc> </func> <func> - <name>list_blocked_users(Port) -> Users | []</name> - <name>list_blocked_users(Address, Port) -> Users | []</name> - <name>list_blocked_users(Port, Dir) -> Users | []</name> - <name>list_blocked_users(Address, Port, Dir) -> Users | []</name> + <name since="">list_blocked_users(Port) -> Users | []</name> + <name since="">list_blocked_users(Address, Port) -> Users | []</name> + <name since="">list_blocked_users(Port, Dir) -> Users | []</name> + <name since="">list_blocked_users(Address, Port, Dir) -> Users | []</name> <fsummary>Lists users that are currently blocked from access to a specified port number, for a given address (if specified).</fsummary> <type> @@ -97,10 +97,10 @@ </func> <func> - <name>unblock_user(User, Port) -> true | {error, Reason}</name> - <name>unblock_user(User, Address, Port) -> true | {error, Reason}</name> - <name>unblock_user(User, Port, Dir) -> true | {error, Reason}</name> - <name>unblock_user(User, Address, Port, Dir) -> true | {error, Reason}</name> + <name since="">unblock_user(User, Port) -> true | {error, Reason}</name> + <name since="">unblock_user(User, Address, Port) -> true | {error, Reason}</name> + <name since="">unblock_user(User, Port, Dir) -> true | {error, Reason}</name> + <name since="">unblock_user(User, Address, Port, Dir) -> true | {error, Reason}</name> <fsummary>Removes a blocked user from the block list.</fsummary> <type> <v>User = string()</v> @@ -129,8 +129,8 @@ <funcs> <func> - <name>Module:event(What, Port, Dir, Data) -> ignored</name> - <name>Module:event(What, Address, Port, Dir, Data) -> ignored</name> + <name since="OTP 18.1">Module:event(What, Port, Dir, Data) -> ignored</name> + <name since="OTP 18.1">Module:event(What, Address, Port, Dir, Data) -> ignored</name> <fsummary>Called whenever an event occurs in <c>mod_security</c>.</fsummary> <type> <v>What = atom()</v> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index e34dd78a2a..dd8c4115a5 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,31 @@ <file>notes.xml</file> </header> - <section><title>Inets 7.0.2</title> + <section><title>Inets 7.0.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed http client to not send 'content-length' header in + chunked encoded requests.</p> + <p> + Own Id: OTP-15338 Aux Id: ERL-733 </p> + </item> + <item> + <p> + Fixed http client to not drop explicit 'Content-Type' + header in requests without a body such as requests with + the 'Content-Type' of application/x-www-form-urlencoded.</p> + <p> + Own Id: OTP-15339 Aux Id: ERL-736 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 7.0.2</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 1bf5d25c98..8b356d8026 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -805,11 +805,12 @@ handle_unix_socket_options(#request{unix_socket = UnixSocket}, error({badarg, [{ipfamily, Else}, {unix_socket, UnixSocket}]}) end. -connect_and_send_first_request(Address, Request, #state{options = Options0} = State) -> +connect_and_send_first_request(Address, #request{ipv6_host_with_brackets = HasBrackets} = Request, + #state{options = Options0} = State) -> SocketType = socket_type(Request), ConnTimeout = (Request#request.settings)#http_options.connect_timeout, Options = handle_unix_socket_options(Request, Options0), - case connect(SocketType, Address, Options, ConnTimeout) of + case connect(SocketType, format_address(Address, HasBrackets), Options, ConnTimeout) of {ok, Socket} -> ClientClose = httpc_request:is_client_closing( @@ -1739,3 +1740,8 @@ update_session(ProfileName, #session{id = SessionId} = Session, Pos, Value) -> end. +format_address({Host, Port}, true) when is_list(Host)-> + {ok, Address} = inet:parse_address(string:strip(string:strip(Host, right, $]), left, $[)), + {Address, Port}; +format_address(HostPort, _) -> + HostPort. diff --git a/lib/inets/test/httpd_bench_SUITE.erl b/lib/inets/test/httpd_bench_SUITE.erl index 4b549dcb5b..087516f56c 100644 --- a/lib/inets/test/httpd_bench_SUITE.erl +++ b/lib/inets/test/httpd_bench_SUITE.erl @@ -37,7 +37,8 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- suite() -> - [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. + [{timetrap, {minutes, 1}}, + {ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. all() -> [ @@ -499,8 +500,8 @@ start_dummy("http"= Protocol, Config) -> Conf = [ %%{big, filename:join(DataDir, "1M_file")}, %%{small, filename:join(DataDir, "1k_file")}, - {big, {gen, crypto:rand_bytes(1000000)}}, - {small, {gen, crypto:rand_bytes(1000)}}, + {big, {gen, crypto:strong_rand_bytes(1000000)}}, + {small, {gen, crypto:strong_rand_bytes(1000)}}, {http_version, HTTPVersion}, {keep_alive, ?config(keep_alive, Config)} ], @@ -519,8 +520,8 @@ start_dummy("https" = Protocol, Config) -> Opts = [{active, true}, {nodelay, true}, {reuseaddr, true} | SSLOpts], Conf = [%%{big, filename:join(DataDir, "1M_file")}, %%{small, filename:join(DataDir, "1k_file")}, - {big, {gen, crypto:rand_bytes(1000000)}}, - {small, {gen, crypto:rand_bytes(1000)}}, + {big, {gen, crypto:strong_rand_bytes(1000000)}}, + {small, {gen, crypto:strong_rand_bytes(1000)}}, {http_version, HTTPVersion}, {keep_alive, ?config(keep_alive, Config)} ], diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 26adb854e1..52c05a7974 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 7.0.2 +INETS_VSN = 7.0.3 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml index 38c7b5acf1..4e32c1a3a5 100644 --- a/lib/kernel/doc/src/application.xml +++ b/lib/kernel/doc/src/application.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>application</module> + <module since="">application</module> <modulesummary>Generic OTP application functions</modulesummary> <description> <p>In OTP, <em>application</em> denotes a component implementing @@ -67,8 +67,8 @@ </datatypes> <funcs> <func> - <name name="ensure_all_started" arity="1"/> - <name name="ensure_all_started" arity="2"/> + <name name="ensure_all_started" arity="1" since="OTP R16B02"/> + <name name="ensure_all_started" arity="2" since="OTP R16B02"/> <fsummary>Load and start an application and its dependencies, recursively.</fsummary> <desc> <p>Equivalent to calling @@ -85,8 +85,8 @@ </desc> </func> <func> - <name name="ensure_started" arity="1"/> - <name name="ensure_started" arity="2"/> + <name name="ensure_started" arity="1" since="OTP R16B01"/> + <name name="ensure_started" arity="2" since="OTP R16B01"/> <fsummary>Load and start an application.</fsummary> <desc> <p>Equivalent to @@ -95,8 +95,8 @@ </desc> </func> <func> - <name name="get_all_env" arity="0"/> - <name name="get_all_env" arity="1"/> + <name name="get_all_env" arity="0" since=""/> + <name name="get_all_env" arity="1" since=""/> <fsummary>Get the configuration parameters for an application.</fsummary> <desc> <p>Returns the configuration parameters and their values for @@ -108,8 +108,8 @@ </desc> </func> <func> - <name name="get_all_key" arity="0"/> - <name name="get_all_key" arity="1"/> + <name name="get_all_key" arity="0" since=""/> + <name name="get_all_key" arity="1" since=""/> <fsummary>Get the application specification keys.</fsummary> <desc> <p>Returns the application specification keys and their values @@ -122,8 +122,8 @@ </desc> </func> <func> - <name name="get_application" arity="0"/> - <name name="get_application" arity="1"/> + <name name="get_application" arity="0" since=""/> + <name name="get_application" arity="1" since=""/> <fsummary>Get the name of an application containing a certain process or module.</fsummary> <desc> <p>Returns the name of the application to which the process @@ -136,8 +136,8 @@ </desc> </func> <func> - <name name="get_env" arity="1"/> - <name name="get_env" arity="2"/> + <name name="get_env" arity="1" since=""/> + <name name="get_env" arity="2" since=""/> <fsummary>Get the value of a configuration parameter.</fsummary> <desc> <p>Returns the value of configuration parameter <c><anno>Par</anno></c> @@ -153,7 +153,7 @@ </desc> </func> <func> - <name name="get_env" arity="3"/> + <name name="get_env" arity="3" since="OTP R16B"/> <fsummary>Get the value of a configuration parameter using a default.</fsummary> <desc> <p>Works like <seealso marker="#get_env/2"><c>get_env/2</c></seealso> but returns @@ -162,8 +162,8 @@ </desc> </func> <func> - <name name="get_key" arity="1"/> - <name name="get_key" arity="2"/> + <name name="get_key" arity="1" since=""/> + <name name="get_key" arity="2" since=""/> <fsummary>Get the value of an application specification key.</fsummary> <desc> <p>Returns the value of the application specification key @@ -180,8 +180,8 @@ </desc> </func> <func> - <name name="load" arity="1"/> - <name name="load" arity="2"/> + <name name="load" arity="1" since=""/> + <name name="load" arity="2" since=""/> <fsummary>Load an application.</fsummary> <type name="application_spec"/> <type name="application_opt"/> @@ -226,7 +226,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="loaded_applications" arity="0"/> + <name name="loaded_applications" arity="0" since=""/> <fsummary>Get the currently loaded applications.</fsummary> <desc> <p>Returns a list with information about the applications, and included @@ -238,7 +238,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="permit" arity="2"/> + <name name="permit" arity="2" since=""/> <fsummary>Change the permission for an application to run at a node.</fsummary> <desc> <p>Changes the permission for <c><anno>Application</anno></c> to run at @@ -271,8 +271,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="set_env" arity="3"/> - <name name="set_env" arity="4"/> + <name name="set_env" arity="3" since=""/> + <name name="set_env" arity="4" since=""/> <fsummary>Set the value of a configuration parameter.</fsummary> <desc> <p>Sets the value of configuration parameter <c><anno>Par</anno></c> for @@ -302,8 +302,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="start" arity="1"/> - <name name="start" arity="2"/> + <name name="start" arity="1" since=""/> + <name name="start" arity="2" since=""/> <fsummary>Load and start an application.</fsummary> <desc> <p>Starts <c><anno>Application</anno></c>. If it is not loaded, @@ -353,7 +353,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="start_type" arity="0"/> + <name name="start_type" arity="0" since=""/> <fsummary>Get the start type of an ongoing application startup.</fsummary> <desc> <p>This function is intended to be called by a process belonging @@ -370,7 +370,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="stop" arity="1"/> + <name name="stop" arity="1" since=""/> <fsummary>Stop an application.</fsummary> <desc> <p>Stops <c><anno>Application</anno></c>. The application master calls @@ -399,7 +399,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="takeover" arity="2"/> + <name name="takeover" arity="2" since=""/> <fsummary>Take over a distributed application.</fsummary> <desc> <p>Takes over the distributed application @@ -424,7 +424,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="unload" arity="1"/> + <name name="unload" arity="1" since=""/> <fsummary>Unload an application.</fsummary> <desc> <p>Unloads the application specification for <c><anno>Application</anno></c> @@ -435,8 +435,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="unset_env" arity="2"/> - <name name="unset_env" arity="3"/> + <name name="unset_env" arity="2" since=""/> + <name name="unset_env" arity="3" since=""/> <fsummary>Unset the value of a configuration parameter.</fsummary> <desc> <p>Removes the configuration parameter <c><anno>Par</anno></c> and its value @@ -459,8 +459,8 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name name="which_applications" arity="0"/> - <name name="which_applications" arity="1"/> + <name name="which_applications" arity="0" since=""/> + <name name="which_applications" arity="1" since=""/> <fsummary>Get the currently running applications.</fsummary> <desc> <p>Returns a list with information about the applications that @@ -484,7 +484,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </section> <funcs> <func> - <name>Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name> + <name since="">Module:start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State} | {error, Reason}</name> <fsummary>Start an application.</fsummary> <type> <v>StartType = <seealso marker="#type-start_type"><c>start_type()</c></seealso></v> @@ -526,7 +526,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name> + <name since="">Module:start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}</name> <fsummary>Extended start of an application.</fsummary> <type> <v>Phase = atom()</v> @@ -551,7 +551,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:prep_stop(State) -> NewState</name> + <name since="">Module:prep_stop(State) -> NewState</name> <fsummary>Prepare an application for termination.</fsummary> <type> <v>State = NewState = term()</v> @@ -569,7 +569,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:stop(State)</name> + <name since="">Module:stop(State)</name> <fsummary>Clean up after termination of an application.</fsummary> <type> <v>State = term()</v> @@ -585,7 +585,7 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> - <name>Module:config_change(Changed, New, Removed) -> ok</name> + <name since="">Module:config_change(Changed, New, Removed) -> ok</name> <fsummary>Update the configuration parameters for an application.</fsummary> <type> <v>Changed = [{Par,Val}]</v> diff --git a/lib/kernel/doc/src/auth.xml b/lib/kernel/doc/src/auth.xml index 5901446960..a57da18de9 100644 --- a/lib/kernel/doc/src/auth.xml +++ b/lib/kernel/doc/src/auth.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>auth</module> + <module since="">auth</module> <modulesummary>Erlang network authentication server.</modulesummary> <description> <p>This module is deprecated. For a description of the Magic @@ -42,7 +42,7 @@ </datatypes> <funcs> <func> - <name name="cookie" arity="0"/> + <name name="cookie" arity="0" since=""/> <fsummary>Magic cookie for local node (deprecated).</fsummary> <desc> <p>Use @@ -51,7 +51,7 @@ </desc> </func> <func> - <name name="cookie" arity="1"/> + <name name="cookie" arity="1" since=""/> <fsummary>Set the magic for the local node (deprecated).</fsummary> <type_desc variable="TheCookie"> The cookie can also be specified as a list with a single atom element. @@ -63,7 +63,7 @@ </desc> </func> <func> - <name name="is_auth" arity="1"/> + <name name="is_auth" arity="1" since=""/> <fsummary>Status of communication authorization (deprecated).</fsummary> <desc> <p>Returns <c>yes</c> if communication with <c><anno>Node</anno></c> is @@ -76,7 +76,7 @@ </desc> </func> <func> - <name>node_cookie([Node, Cookie]) -> yes | no</name> + <name since="">node_cookie([Node, Cookie]) -> yes | no</name> <fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary> <type> <v>Node = node()</v> @@ -88,7 +88,7 @@ </desc> </func> <func> - <name name="node_cookie" arity="2"/> + <name name="node_cookie" arity="2" since=""/> <fsummary>Set the magic cookie for a node and verify authorization (deprecated).</fsummary> <desc> <p>Sets the magic cookie of <c><anno>Node</anno></c> to diff --git a/lib/kernel/doc/src/code.xml b/lib/kernel/doc/src/code.xml index 69ce4da61c..4aa9e8b9d2 100644 --- a/lib/kernel/doc/src/code.xml +++ b/lib/kernel/doc/src/code.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>code</module> + <module since="">code</module> <modulesummary>Erlang code server.</modulesummary> <description> <p>This module contains the interface to the Erlang @@ -322,7 +322,7 @@ zip:create("mnesia-4.4.7.ez", <funcs> <func> - <name name="set_path" arity="1"/> + <name name="set_path" arity="1" since=""/> <fsummary>Set the code server search path.</fsummary> <desc> <p>Sets the code path to the list of directories <c><anno>Path</anno></c>.</p> @@ -336,15 +336,15 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="get_path" arity="0"/> + <name name="get_path" arity="0" since=""/> <fsummary>Return the code server search path.</fsummary> <desc> <p>Returns the code path.</p> </desc> </func> <func> - <name name="add_path" arity="1"/> - <name name="add_pathz" arity="1"/> + <name name="add_path" arity="1" since=""/> + <name name="add_pathz" arity="1" since=""/> <fsummary>Add a directory to the end of the code path.</fsummary> <type name="add_path_ret"/> <desc> @@ -357,7 +357,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="add_patha" arity="1"/> + <name name="add_patha" arity="1" since=""/> <fsummary>Add a directory to the beginning of the code path.</fsummary> <type name="add_path_ret"/> <desc> @@ -370,8 +370,8 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="add_paths" arity="1"/> - <name name="add_pathsz" arity="1"/> + <name name="add_paths" arity="1" since=""/> + <name name="add_pathsz" arity="1" since=""/> <fsummary>Add directories to the end of the code path.</fsummary> <desc> <p>Adds the directories in <c><anno>Dirs</anno></c> to the end of the code @@ -381,7 +381,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="add_pathsa" arity="1"/> + <name name="add_pathsa" arity="1" since=""/> <fsummary>Add directories to the beginning of the code path.</fsummary> <desc> <p>Traverses <c><anno>Dirs</anno></c> and adds @@ -397,7 +397,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="del_path" arity="1"/> + <name name="del_path" arity="1" since=""/> <fsummary>Delete a directory from the code path.</fsummary> <desc> <p>Deletes a directory from the code path. The argument can be @@ -417,7 +417,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="replace_path" arity="2"/> + <name name="replace_path" arity="2" since=""/> <fsummary>Replace a directory with another in the code path.</fsummary> <desc> <p>Replaces an old occurrence of a directory @@ -441,7 +441,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="load_file" arity="1"/> + <name name="load_file" arity="1" since=""/> <fsummary>Load a module.</fsummary> <type name="load_ret"/> <desc> @@ -460,7 +460,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="load_abs" arity="1"/> + <name name="load_abs" arity="1" since=""/> <fsummary>Load a module, residing in a specified file.</fsummary> <type name="load_ret"/> <type name="loaded_filename"/> @@ -477,7 +477,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="ensure_loaded" arity="1"/> + <name name="ensure_loaded" arity="1" since=""/> <fsummary>Ensure that a module is loaded.</fsummary> <desc> <p>Tries to load a module in the same way as @@ -489,7 +489,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="load_binary" arity="3"/> + <name name="load_binary" arity="3" since=""/> <fsummary>Load object code for a module.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -507,7 +507,7 @@ zip:create("mnesia-4.4.7.ez", </desc> </func> <func> - <name name="atomic_load" arity="1"/> + <name name="atomic_load" arity="1" since="OTP 19.0"/> <fsummary>Load a list of modules atomically</fsummary> <desc> <p>Tries to load all of the modules in the list @@ -566,7 +566,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="prepare_loading" arity="1"/> + <name name="prepare_loading" arity="1" since="OTP 19.0"/> <fsummary>Prepare a list of modules atomically</fsummary> <desc> <p>Prepares to load the modules in the list @@ -598,7 +598,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="finish_loading" arity="1"/> + <name name="finish_loading" arity="1" since="OTP 19.0"/> <fsummary>Finish loading a list of prepared modules atomically</fsummary> <desc> <p>Tries to load code for all modules that have been previously @@ -627,7 +627,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="ensure_modules_loaded" arity="1"/> + <name name="ensure_modules_loaded" arity="1" since="OTP 19.0"/> <fsummary>Ensure that a list of modules is loaded</fsummary> <desc> <p>Tries to load any modules not already loaded in the list @@ -639,7 +639,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Remove current code for a module.</fsummary> <desc> <p>Removes the current code for <c><anno>Module</anno></c>, that is, @@ -652,7 +652,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="purge" arity="1"/> + <name name="purge" arity="1" since=""/> <fsummary>Remove old code for a module.</fsummary> <desc> <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code @@ -668,7 +668,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="soft_purge" arity="1"/> + <name name="soft_purge" arity="1" since=""/> <fsummary>Remove old code for a module, unless no process uses it.</fsummary> <desc> <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code @@ -683,7 +683,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="is_loaded" arity="1"/> + <name name="is_loaded" arity="1" since=""/> <fsummary>Check if a module is loaded.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -702,7 +702,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="all_loaded" arity="0"/> + <name name="all_loaded" arity="0" since=""/> <fsummary>Get all loaded modules.</fsummary> <type name="loaded_filename"/> <type name="loaded_ret_atoms"/> @@ -716,7 +716,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="which" arity="1"/> + <name name="which" arity="1" since=""/> <fsummary>The object code file of a module.</fsummary> <type name="loaded_ret_atoms"/> <desc> @@ -731,7 +731,7 @@ ok = code:finish_loading(Prepared), </desc> </func> <func> - <name name="get_object_code" arity="1"/> + <name name="get_object_code" arity="1" since=""/> <fsummary>Gets the object code for a module.</fsummary> <desc> <p>Searches the code path for the object code of module @@ -750,7 +750,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="root_dir" arity="0"/> + <name name="root_dir" arity="0" since=""/> <fsummary>Root directory of Erlang/OTP.</fsummary> <desc> <p>Returns the root directory of Erlang/OTP, which is @@ -762,7 +762,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="lib_dir" arity="0"/> + <name name="lib_dir" arity="0" since=""/> <fsummary>Library directory of Erlang/OTP.</fsummary> <desc> <p>Returns the library directory, <c>$OTPROOT/lib</c>, where @@ -774,7 +774,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="lib_dir" arity="1"/> + <name name="lib_dir" arity="1" since=""/> <fsummary>Library directory for an application.</fsummary> <desc> <p>Returns the path @@ -807,7 +807,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="lib_dir" arity="2"/> + <name name="lib_dir" arity="2" since=""/> <fsummary>Subdirectory for an application.</fsummary> <desc> <p>Returns the path to a subdirectory directly under the top @@ -827,7 +827,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="compiler_dir" arity="0"/> + <name name="compiler_dir" arity="0" since=""/> <fsummary>Library directory for the compiler.</fsummary> <desc> <p>Returns the compiler library directory. Equivalent to @@ -835,7 +835,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="priv_dir" arity="1"/> + <name name="priv_dir" arity="1" since=""/> <fsummary>Priv directory for an application.</fsummary> <desc> <p>Returns the path to the <c>priv</c> directory in an @@ -846,7 +846,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="objfile_extension" arity="0"/> + <name name="objfile_extension" arity="0" since=""/> <fsummary>Object code file extension.</fsummary> <desc> <p>Returns the object code file extension corresponding to @@ -854,7 +854,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="stick_dir" arity="1"/> + <name name="stick_dir" arity="1" since=""/> <fsummary>Mark a directory as sticky.</fsummary> <desc> <p>Marks <c><anno>Dir</anno></c> as sticky.</p> @@ -862,7 +862,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="unstick_dir" arity="1"/> + <name name="unstick_dir" arity="1" since=""/> <fsummary>Remove a sticky directory mark.</fsummary> <desc> <p>Unsticks a directory that is marked as @@ -871,7 +871,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="is_sticky" arity="1"/> + <name name="is_sticky" arity="1" since=""/> <fsummary>Test if a module is sticky.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Module</anno></c> is the @@ -882,7 +882,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="where_is_file" arity="1"/> + <name name="where_is_file" arity="1" since=""/> <fsummary>Full name of a file located in the code path.</fsummary> <desc> <p>Searches the code path for <c><anno>Filename</anno></c>, a file of @@ -893,7 +893,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="clash" arity="0"/> + <name name="clash" arity="0" since=""/> <fsummary>Search for modules with identical names.</fsummary> <desc> <p>Searches all directories in the code path for module names with @@ -901,7 +901,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="module_status" arity="1"/> + <name name="module_status" arity="1" since="OTP 20.0"/> <fsummary>Return the status of the module in relation to object file on disk.</fsummary> <desc> <p>Returns:</p> @@ -934,7 +934,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="modified_modules" arity="0"/> + <name name="modified_modules" arity="0" since="OTP 20.0"/> <fsummary>Return a list of all modules modified on disk.</fsummary> <desc> <p>Returns the list of all currently loaded modules for which @@ -943,7 +943,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </desc> </func> <func> - <name name="is_module_native" arity="1"/> + <name name="is_module_native" arity="1" since=""/> <fsummary>Test if a module has native code.</fsummary> <desc> <p>Returns:</p> @@ -961,7 +961,7 @@ rpc:call(Node, code, load_binary, [Module, Filename, Binary]), </func> <func> - <name name="get_mode" arity="0"/> + <name name="get_mode" arity="0" since="OTP R16B"/> <fsummary>The mode of the code server.</fsummary> <desc> <p>Returns an atom describing the mode of the code server: diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml index 884cb32c0c..e308b06f3c 100644 --- a/lib/kernel/doc/src/disk_log.xml +++ b/lib/kernel/doc/src/disk_log.xml @@ -34,7 +34,7 @@ <rev>D</rev> <file>disk_log.sgml</file> </header> - <module>disk_log</module> + <module since="">disk_log</module> <modulesummary>A disk-based term logging facility.</modulesummary> <description> <p><c>disk_log</c> is a disk-based term logger that enables @@ -238,7 +238,7 @@ </datatypes> <funcs> <func> - <name name="accessible_logs" arity="0"/> + <name name="accessible_logs" arity="0" since=""/> <fsummary>Return the accessible disk logs on the current node.</fsummary> <desc> <p>Returns the names of the disk logs accessible on the current node. @@ -248,8 +248,8 @@ </desc> </func> <func> - <name name="alog" arity="2"/> - <name name="balog" arity="2"/> + <name name="alog" arity="2" since=""/> + <name name="balog" arity="2" since=""/> <fsummary>Asynchronously log an item on to a disk log.</fsummary> <type variable="Log"/> <type variable="Term" name_i="1"/> @@ -275,8 +275,8 @@ </desc> </func> <func> - <name name="alog_terms" arity="2"/> - <name name="balog_terms" arity="2"/> + <name name="alog_terms" arity="2" since=""/> + <name name="balog_terms" arity="2" since=""/> <fsummary>Asynchronously log many items on to a disk log.</fsummary> <type variable="Log"/> <type variable="TermList" name_i="1"/> @@ -303,8 +303,8 @@ </desc> </func> <func> - <name name="block" arity="1"/> - <name name="block" arity="2"/> + <name name="block" arity="1" since=""/> + <name name="block" arity="2" since=""/> <fsummary>Block a disk log.</fsummary> <type name="block_error_rsn"/> <desc> @@ -330,21 +330,21 @@ </desc> </func> <func> - <name name="change_header" arity="2"/> + <name name="change_header" arity="2" since=""/> <fsummary>Change option head or head_func for an owner of a disk log.</fsummary> <desc> <p>Changes the value of option <c>head</c> or <c>head_func</c> for an owner of a disk log.</p> </desc> </func> <func> - <name name="change_notify" arity="3"/> + <name name="change_notify" arity="3" since=""/> <fsummary>Change option notify for an owner of a disk log.</fsummary> <desc> <p>Changes the value of option <c>notify</c> for an owner of a disk log. </p> </desc> </func> <func> - <name name="change_size" arity="2"/> + <name name="change_size" arity="2" since=""/> <fsummary>Change the size of an open disk log.</fsummary> <desc> <p>Changes the size of an open log. @@ -384,10 +384,10 @@ </desc> </func> <func> - <name name="chunk" arity="2"/> - <name name="chunk" arity="3"/> - <name name="bchunk" arity="2"/> - <name name="bchunk" arity="3"/> + <name name="chunk" arity="2" since=""/> + <name name="chunk" arity="3" since=""/> + <name name="bchunk" arity="2" since=""/> + <name name="bchunk" arity="3" since=""/> <fsummary>Read a chunk of items written to a disk log.</fsummary> <type variable="Log"/> <type variable="Continuation"/> @@ -447,7 +447,7 @@ </desc> </func> <func> - <name name="chunk_info" arity="1"/> + <name name="chunk_info" arity="1" since=""/> <fsummary>Return information about a chunk continuation of a disk log.</fsummary> <desc> <p>Returns the pair <c>{node, <anno>Node</anno>}</c>, @@ -457,7 +457,7 @@ </desc> </func> <func> - <name name="chunk_step" arity="3"/> + <name name="chunk_step" arity="3" since=""/> <fsummary>Step forward or backward among the wrap log files of a disk log.</fsummary> <desc> <p>Can be used with <c>chunk/2,3</c> and <c>bchunk/2,3</c> @@ -480,7 +480,7 @@ </desc> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a disk log.</fsummary> <type name="close_error_rsn"/> <desc> @@ -505,7 +505,7 @@ </desc> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return an English description of a disk log error reply.</fsummary> <desc> <p>Given the error returned by any function in this module, @@ -517,7 +517,7 @@ </desc> </func> <func> - <name name="inc_wrap_file" arity="1"/> + <name name="inc_wrap_file" arity="1" since=""/> <fsummary>Change to the next wrap log file of a disk log.</fsummary> <type name="inc_wrap_error_rsn"/> <type name="invalid_header"/> @@ -534,7 +534,7 @@ </desc> </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Return information about a disk log.</fsummary> <type name="dlog_info"/> <desc> @@ -685,8 +685,8 @@ </desc> </func> <func> - <name name="lclose" arity="1"/> - <name name="lclose" arity="2"/> + <name name="lclose" arity="1" since=""/> + <name name="lclose" arity="2" since=""/> <fsummary>Close a disk log on one node.</fsummary> <type name="lclose_error_rsn"/> <desc> @@ -704,8 +704,8 @@ </desc> </func> <func> - <name name="log" arity="2"/> - <name name="blog" arity="2"/> + <name name="log" arity="2" since=""/> + <name name="blog" arity="2" since=""/> <fsummary>Log an item onto a disk log.</fsummary> <type variable="Log"/> <type variable="Term" name_i="1"/> @@ -739,8 +739,8 @@ </desc> </func> <func> - <name name="log_terms" arity="2"/> - <name name="blog_terms" arity="2"/> + <name name="log_terms" arity="2" since=""/> + <name name="blog_terms" arity="2" since=""/> <fsummary>Log many items onto a disk log.</fsummary> <type variable="Log"/> <type variable="TermList" name_i="1"/> @@ -768,7 +768,7 @@ </desc> </func> <func> - <name name="open" arity="1"/> + <name name="open" arity="1" since=""/> <fsummary>Open a disk log file.</fsummary> <type name="dlog_options"/> <type name="dlog_option"/> @@ -1041,7 +1041,7 @@ </desc> </func> <func> - <name name="pid2name" arity="1"/> + <name name="pid2name" arity="1" since=""/> <fsummary>Return the name of the disk log handled by a pid.</fsummary> <desc> <p>Returns the log name @@ -1053,9 +1053,9 @@ </desc> </func> <func> - <name name="reopen" arity="2"/> - <name name="reopen" arity="3"/> - <name name="breopen" arity="3"/> + <name name="reopen" arity="2" since=""/> + <name name="reopen" arity="3" since=""/> + <name name="breopen" arity="3" since=""/> <fsummary>Reopen a disk log and save the old log.</fsummary> <type variable="Log"/> <type variable="File" name_i="1"/> @@ -1087,7 +1087,7 @@ </desc> </func> <func> - <name name="sync" arity="1"/> + <name name="sync" arity="1" since=""/> <fsummary>Flush the contents of a disk log to the disk.</fsummary> <type name="sync_error_rsn"/> <desc> @@ -1097,9 +1097,9 @@ </desc> </func> <func> - <name name="truncate" arity="1"/> - <name name="truncate" arity="2"/> - <name name="btruncate" arity="2"/> + <name name="truncate" arity="1" since=""/> + <name name="truncate" arity="2" since=""/> + <name name="btruncate" arity="2" since=""/> <fsummary>Truncate a disk log.</fsummary> <type variable="Log"/> <type variable="Head" name_i="2"/> @@ -1129,7 +1129,7 @@ </desc> </func> <func> - <name name="unblock" arity="1"/> + <name name="unblock" arity="1" since=""/> <fsummary>Unblock a disk log.</fsummary> <type name="unblock_error_rsn"/> <desc> diff --git a/lib/kernel/doc/src/erl_boot_server.xml b/lib/kernel/doc/src/erl_boot_server.xml index 4109251387..89f9855c49 100644 --- a/lib/kernel/doc/src/erl_boot_server.xml +++ b/lib/kernel/doc/src/erl_boot_server.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>erl_boot_server</module> + <module since="">erl_boot_server</module> <modulesummary>Boot server for other Erlang machines.</modulesummary> <description> <p>This server is used to assist diskless Erlang nodes that fetch @@ -52,14 +52,14 @@ </description> <funcs> <func> - <name name="add_slave" arity="1"/> + <name name="add_slave" arity="1" since=""/> <fsummary>Add a slave to the list of allowed slaves.</fsummary> <desc> <p>Adds a <c><anno>Slave</anno></c> node to the list of allowed slave hosts.</p> </desc> </func> <func> - <name name="delete_slave" arity="1"/> + <name name="delete_slave" arity="1" since=""/> <fsummary>Delete a slave from the list of allowed slaves.</fsummary> <desc> <p>Deletes a <c><anno>Slave</anno></c> node from the list of allowed slave @@ -67,7 +67,7 @@ </desc> </func> <func> - <name name="start" arity="1"/> + <name name="start" arity="1" since=""/> <fsummary>Start the boot server.</fsummary> <desc> <p>Starts the boot server. <c><anno>Slaves</anno></c> is a list of @@ -76,7 +76,7 @@ </desc> </func> <func> - <name name="start_link" arity="1"/> + <name name="start_link" arity="1" since=""/> <fsummary>Start the boot server and link to the the caller.</fsummary> <desc> <p>Starts the boot server and links to the caller. This function @@ -85,7 +85,7 @@ </desc> </func> <func> - <name name="which_slaves" arity="0"/> + <name name="which_slaves" arity="0" since=""/> <fsummary>Return the current list of allowed slave hosts.</fsummary> <desc> <p>Returns the current list of allowed slave hosts.</p> diff --git a/lib/kernel/doc/src/erl_ddll.xml b/lib/kernel/doc/src/erl_ddll.xml index 75114e015c..f2d5e1b397 100644 --- a/lib/kernel/doc/src/erl_ddll.xml +++ b/lib/kernel/doc/src/erl_ddll.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>erl_ddll</module> + <module since="">erl_ddll</module> <modulesummary>Dynamic driver loader and linker.</modulesummary> <description> <p>This module provides an interface for loading @@ -196,7 +196,7 @@ </datatypes> <funcs> <func> - <name name="demonitor" arity="1"/> + <name name="demonitor" arity="1" since=""/> <fsummary>Remove a monitor for a driver.</fsummary> <desc> <p>Removes a driver monitor in much the same way as @@ -212,7 +212,7 @@ </desc> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Format an error descriptor.</fsummary> <desc> <p>Takes an <c><anno>ErrorDesc</anno></c> returned by load, unload, or @@ -229,7 +229,7 @@ </desc> </func> <func> - <name name="info" arity="0"/> + <name name="info" arity="0" since=""/> <fsummary>Retrieve information about all drivers.</fsummary> <desc> <p>Returns a list of tuples <c>{<anno>DriverName</anno>, <anno>InfoList</anno>}</c>, @@ -240,7 +240,7 @@ </desc> </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Retrieve information about one driver.</fsummary> <desc> <p>Returns a list of tuples <c>{<anno>Tag</anno>, <anno>Value</anno>}</c>, @@ -266,7 +266,7 @@ </desc> </func> <func> - <name name="info" arity="2"/> + <name name="info" arity="2" since=""/> <fsummary>Retrieve specific information about one driver.</fsummary> <desc> <p>Returns specific information about one aspect of a driver. @@ -328,7 +328,7 @@ </desc> </func> <func> - <name name="load" arity="2"/> + <name name="load" arity="2" since=""/> <fsummary>Load a driver.</fsummary> <desc> <p>Loads and links the dynamic driver <c><anno>Name</anno></c>. @@ -390,7 +390,7 @@ </desc> </func> <func> - <name name="load_driver" arity="2"/> + <name name="load_driver" arity="2" since=""/> <fsummary>Load a driver.</fsummary> <desc> <p>Works essentially as <c>load/2</c>, but loads the driver @@ -413,7 +413,7 @@ </desc> </func> <func> - <name name="loaded_drivers" arity="0"/> + <name name="loaded_drivers" arity="0" since=""/> <fsummary>List loaded drivers.</fsummary> <desc> <p>Returns a list of all the available drivers, both @@ -425,7 +425,7 @@ </desc> </func> <func> - <name name="monitor" arity="2"/> + <name name="monitor" arity="2" since=""/> <fsummary>Create a monitor for a driver.</fsummary> <desc> <p>Creates a driver monitor and works in many @@ -588,7 +588,7 @@ </desc> </func> <func> - <name name="reload" arity="2"/> + <name name="reload" arity="2" since=""/> <fsummary>Replace a driver.</fsummary> <desc> <p>Reloads the driver named <c><anno>Name</anno></c> from a possibly @@ -626,7 +626,7 @@ </desc> </func> <func> - <name name="reload_driver" arity="2"/> + <name name="reload_driver" arity="2" since=""/> <fsummary>Replace a driver.</fsummary> <desc> <p>Works exactly as <seealso marker="#reload/2"><c>reload/2</c></seealso>, @@ -644,7 +644,7 @@ </desc> </func> <func> - <name name="try_load" arity="3"/> + <name name="try_load" arity="3" since=""/> <fsummary>Load a driver.</fsummary> <desc> <p>Provides more control than the @@ -931,7 +931,7 @@ </desc> </func> <func> - <name name="try_unload" arity="2"/> + <name name="try_unload" arity="2" since=""/> <fsummary>Unload a driver.</fsummary> <desc> <p>This is the low-level function to unload (or decrement @@ -1116,7 +1116,7 @@ </desc> </func> <func> - <name name="unload" arity="1"/> + <name name="unload" arity="1" since=""/> <fsummary>Unload a driver.</fsummary> <desc> <p>Unloads, or at least dereferences the driver named @@ -1143,7 +1143,7 @@ </desc> </func> <func> - <name name="unload_driver" arity="1"/> + <name name="unload_driver" arity="1" since=""/> <fsummary>Unload a driver.</fsummary> <desc> <p>Unloads, or at least dereferences the driver named diff --git a/lib/kernel/doc/src/erl_epmd.xml b/lib/kernel/doc/src/erl_epmd.xml index 8b076cd2d7..2adbf11a28 100644 --- a/lib/kernel/doc/src/erl_epmd.xml +++ b/lib/kernel/doc/src/erl_epmd.xml @@ -28,7 +28,7 @@ <date>2018-02-19</date> <rev>A</rev> </header> - <module>erl_epmd</module> + <module since="OTP R14B">erl_epmd</module> <modulesummary> Erlang interface towards epmd </modulesummary> @@ -41,7 +41,7 @@ <funcs> <func> - <name name="start_link" arity="0"/> + <name name="start_link" arity="0" since="OTP 21.0"/> <fsummary>Callback for erl_distribution supervisor.</fsummary> <desc> <p>This function is invoked as this module is added as a child of the @@ -50,8 +50,8 @@ </func> <func> - <name name="register_node" arity="2"/> - <name name="register_node" arity="3"/> + <name name="register_node" arity="2" since="OTP 21.0"/> + <name name="register_node" arity="3" since="OTP 21.0"/> <fsummary>Registers the node with <c>epmd</c>.</fsummary> <desc> <p>Registers the node with <c>epmd</c> and tells epmd what port will be @@ -62,8 +62,8 @@ </func> <func> - <name name="port_please" arity="2"/> - <name name="port_please" arity="3"/> + <name name="port_please" arity="2" since="OTP 21.0"/> + <name name="port_please" arity="3" since="OTP 21.0"/> <fsummary>Returns the port number for a given node.</fsummary> <desc> <p>Requests the distribution port for the given node of an EPMD @@ -73,7 +73,7 @@ </func> <func> - <name name="address_please" arity="3"/> + <name name="address_please" arity="3" since="OTP 21.0"/> <fsummary>Returns address and port.</fsummary> <desc> <p>Called by the distribution module. Resolves the <c>Host</c> to an IP @@ -84,7 +84,7 @@ </func> <func> - <name name="names" arity="1"/> + <name name="names" arity="1" since="OTP 21.0"/> <fsummary>Names of Erlang nodes at a host.</fsummary> <desc> <p>Called by <seealso marker="net_adm"><c>net_adm:names/0</c></seealso>. diff --git a/lib/kernel/doc/src/error_handler.xml b/lib/kernel/doc/src/error_handler.xml index e5639487dc..eb01e87aee 100644 --- a/lib/kernel/doc/src/error_handler.xml +++ b/lib/kernel/doc/src/error_handler.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>error_handler</module> + <module since="">error_handler</module> <modulesummary>Default system error handler.</modulesummary> <description> <p>This module defines what happens when certain types @@ -38,7 +38,7 @@ </description> <funcs> <func> - <name name="raise_undef_exception" arity="3"/> + <name name="raise_undef_exception" arity="3" since="OTP R16B"/> <fsummary>Raise an undef exception.</fsummary> <type_desc variable="Args"> A (possibly empty) list of arguments <c>Arg1,..,ArgN</c> @@ -51,7 +51,7 @@ </desc> </func> <func> - <name name="undefined_function" arity="3"/> + <name name="undefined_function" arity="3" since=""/> <fsummary>Called when an undefined function is encountered.</fsummary> <type_desc variable="Args"> A (possibly empty) list of arguments <c>Arg1,..,ArgN</c> @@ -93,7 +93,7 @@ </desc> </func> <func> - <name name="undefined_lambda" arity="3"/> + <name name="undefined_lambda" arity="3" since=""/> <fsummary>Called when an undefined lambda (fun) is encountered.</fsummary> <type_desc variable="Args"> A (possibly empty) list of arguments <c>Arg1,..,ArgN</c> diff --git a/lib/kernel/doc/src/error_logger.xml b/lib/kernel/doc/src/error_logger.xml index c3d68fd79f..c170b4fa34 100644 --- a/lib/kernel/doc/src/error_logger.xml +++ b/lib/kernel/doc/src/error_logger.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>error_logger</module> + <module since="">error_logger</module> <modulesummary>Erlang error logger.</modulesummary> <description> @@ -76,8 +76,8 @@ </datatypes> <funcs> <func> - <name name="add_report_handler" arity="1"/> - <name name="add_report_handler" arity="2"/> + <name name="add_report_handler" arity="1" since=""/> + <name name="add_report_handler" arity="2" since=""/> <fsummary>Add an event handler to the error logger.</fsummary> <desc> <p>Adds a new event handler to the error logger. The event @@ -96,7 +96,7 @@ </desc> </func> <func> - <name name="delete_report_handler" arity="1"/> + <name name="delete_report_handler" arity="1" since=""/> <fsummary>Delete an event handler from the error logger.</fsummary> <desc> <p>Deletes an event handler from the error logger by calling @@ -108,9 +108,9 @@ </desc> </func> <func> - <name name="error_msg" arity="1"/> - <name name="error_msg" arity="2"/> - <name name="format" arity="2"/> + <name name="error_msg" arity="1" since=""/> + <name name="error_msg" arity="2" since=""/> + <name name="format" arity="2" since=""/> <fsummary>Log a standard error event.</fsummary> <desc> <p>Log a standard error event. The <c><anno>Format</anno></c> @@ -142,7 +142,7 @@ ok</pre> </desc> </func> <func> - <name name="error_report" arity="1"/> + <name name="error_report" arity="1" since=""/> <fsummary>Log a standard error event.</fsummary> <desc> <p>Log a standard error event. Error logger forwards the event @@ -169,7 +169,7 @@ ok</pre> </desc> </func> <func> - <name name="error_report" arity="2"/> + <name name="error_report" arity="2" since=""/> <fsummary>Log a user-defined error event.</fsummary> <desc> <p>Log a user-defined error event. Error logger forwards the @@ -191,7 +191,7 @@ ok</pre> </desc> </func> <func> - <name name="get_format_depth" arity="0"/> + <name name="get_format_depth" arity="0" since="OTP 20.0"/> <fsummary>Get the value of the Kernel application variable <c>error_logger_format_depth</c>.</fsummary> <desc> @@ -211,8 +211,8 @@ ok</pre> </desc> </func> <func> - <name name="info_msg" arity="1"/> - <name name="info_msg" arity="2"/> + <name name="info_msg" arity="1" since=""/> + <name name="info_msg" arity="2" since=""/> <fsummary>Log a standard information event.</fsummary> <desc> <p>Log a standard information event. The <c><anno>Format</anno></c> @@ -244,7 +244,7 @@ ok</pre> </desc> </func> <func> - <name name="info_report" arity="1"/> + <name name="info_report" arity="1" since=""/> <fsummary>Log a standard information event.</fsummary> <desc> <p>Log a standard information event. Error logger forwards the @@ -271,7 +271,7 @@ ok</pre> </desc> </func> <func> - <name name="info_report" arity="2"/> + <name name="info_report" arity="2" since=""/> <fsummary>Log a user-defined information event.</fsummary> <desc> <p>Log a user-defined information event. Error logger forwards @@ -294,9 +294,9 @@ ok</pre> </desc> </func> <func> - <name name="logfile" arity="1" clause_i="1"/> - <name name="logfile" arity="1" clause_i="2"/> - <name name="logfile" arity="1" clause_i="3"/> + <name name="logfile" arity="1" clause_i="1" since=""/> + <name name="logfile" arity="1" clause_i="2" since=""/> + <name name="logfile" arity="1" clause_i="3" since=""/> <fsummary>Enable or disable error printouts to a file.</fsummary> <type variable="Filename"/> <type variable="OpenReason" name_i="1"/> @@ -346,7 +346,7 @@ ok</pre> </desc> </func> <func> - <name name="tty" arity="1"/> + <name name="tty" arity="1" since=""/> <fsummary>Enable or disable printouts to the terminal.</fsummary> <desc> <p>Enables (<c><anno>Flag</anno> == true</c>) or disables @@ -363,7 +363,7 @@ ok</pre> </desc> </func> <func> - <name name="warning_map" arity="0"/> + <name name="warning_map" arity="0" since=""/> <fsummary>Return the current mapping for warning events.</fsummary> <desc> <p>Returns the current mapping for warning events. Events sent @@ -400,8 +400,8 @@ ok</pre> </desc> </func> <func> - <name name="warning_msg" arity="1"/> - <name name="warning_msg" arity="2"/> + <name name="warning_msg" arity="1" since=""/> + <name name="warning_msg" arity="2" since=""/> <fsummary>Log a standard warning event.</fsummary> <desc> <p>Log a standard warning event. The <c><anno>Format</anno></c> @@ -429,7 +429,7 @@ ok</pre> </desc> </func> <func> - <name name="warning_report" arity="1"/> + <name name="warning_report" arity="1" since=""/> <fsummary>Log a standard warning event.</fsummary> <desc> <p>Log a standard warning event. Error logger forwards the event @@ -446,7 +446,7 @@ ok</pre> </desc> </func> <func> - <name name="warning_report" arity="2"/> + <name name="warning_report" arity="2" since=""/> <fsummary>Log a user-defined warning event.</fsummary> <desc> <p>Log a user-defined warning event. Error logger forwards the diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml index 9acaf6b41e..fc25e83d40 100644 --- a/lib/kernel/doc/src/file.xml +++ b/lib/kernel/doc/src/file.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>file</module> + <module since="">file</module> <modulesummary>File interface module.</modulesummary> <description> <p>This module provides an interface to the file system.</p> @@ -186,7 +186,7 @@ <funcs> <func> - <name name="advise" arity="4"/> + <name name="advise" arity="4" since="OTP R14B"/> <fsummary>Predeclare an access pattern for file data.</fsummary> <type name="posix_file_advise"/> <desc> @@ -197,7 +197,7 @@ </desc> </func> <func> - <name name="allocate" arity="3"/> + <name name="allocate" arity="3" since="OTP R16B"/> <fsummary>Allocate file space.</fsummary> <desc> <p><c>allocate/3</c> can be used to preallocate space for a file.</p> @@ -209,7 +209,7 @@ </desc> </func> <func> - <name name="change_group" arity="2"/> + <name name="change_group" arity="2" since=""/> <fsummary>Change group of a file.</fsummary> <desc> <p>Changes group of a file. See @@ -217,7 +217,7 @@ </desc> </func> <func> - <name name="change_mode" arity="2"/> + <name name="change_mode" arity="2" since="OTP R14B"/> <fsummary>Change permissions of a file.</fsummary> <desc> <p>Changes permissions of a file. See @@ -225,7 +225,7 @@ </desc> </func> <func> - <name name="change_owner" arity="2"/> + <name name="change_owner" arity="2" since=""/> <fsummary>Change owner of a file.</fsummary> <desc> <p>Changes owner of a file. See @@ -233,7 +233,7 @@ </desc> </func> <func> - <name name="change_owner" arity="3"/> + <name name="change_owner" arity="3" since=""/> <fsummary>Change owner and group of a file.</fsummary> <desc> <p>Changes owner and group of a file. See @@ -241,7 +241,7 @@ </desc> </func> <func> - <name name="change_time" arity="2"/> + <name name="change_time" arity="2" since=""/> <fsummary>Change the modification time of a file.</fsummary> <desc> <p>Changes the modification and access times of a file. See @@ -249,7 +249,7 @@ </desc> </func> <func> - <name name="change_time" arity="3"/> + <name name="change_time" arity="3" since=""/> <fsummary>Change the modification and last access time of a file.</fsummary> <desc> <p>Changes the modification and last access times of a file. See @@ -257,7 +257,7 @@ </desc> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a file.</fsummary> <desc> <p>Closes the file referenced by <c><anno>IoDevice</anno></c>. It mostly @@ -270,7 +270,7 @@ </desc> </func> <func> - <name name="consult" arity="1"/> + <name name="consult" arity="1" since=""/> <fsummary>Read Erlang terms from a file.</fsummary> <desc> <p>Reads Erlang terms, separated by '.', from @@ -308,8 +308,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="copy" arity="2"/> - <name name="copy" arity="3"/> + <name name="copy" arity="2" since=""/> + <name name="copy" arity="3" since=""/> <fsummary>Copy file contents.</fsummary> <desc> <p>Copies <c><anno>ByteCount</anno></c> bytes from @@ -346,7 +346,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="datasync" arity="1"/> + <name name="datasync" arity="1" since="OTP R14B"/> <fsummary>Synchronize the in-memory data of a file, ignoring most of its metadata, with that on the physical medium.</fsummary> <desc> <p>Ensures that any buffers kept by the operating system @@ -369,7 +369,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="del_dir" arity="1"/> + <name name="del_dir" arity="1" since=""/> <fsummary>Delete a directory.</fsummary> <desc> <p>Tries to delete directory <c><anno>Dir</anno></c>. @@ -405,7 +405,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Delete a file.</fsummary> <desc> <p>Tries to delete file <c><anno>Filename</anno></c>. @@ -442,7 +442,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="eval" arity="1"/> + <name name="eval" arity="1" since=""/> <fsummary>Evaluate Erlang expressions in a file.</fsummary> <desc> <p>Reads and evaluates Erlang expressions, separated by '.' (or @@ -476,7 +476,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="eval" arity="2"/> + <name name="eval" arity="2" since=""/> <fsummary>Evaluate Erlang expressions in a file.</fsummary> <desc> <p>The same as <c>eval/1</c>, but the variable bindings @@ -486,7 +486,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return a descriptive string for an error reason.</fsummary> <desc> <p>Given the error reason returned by any function in this @@ -494,7 +494,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="get_cwd" arity="0"/> + <name name="get_cwd" arity="0" since=""/> <fsummary>Get the current working directory.</fsummary> <desc> <p>Returns <c>{ok, <anno>Dir</anno>}</c>, where <c><anno>Dir</anno></c> @@ -516,7 +516,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="get_cwd" arity="1"/> + <name name="get_cwd" arity="1" since=""/> <fsummary>Get the current working directory for the specified drive.</fsummary> <desc> <p>Returns <c>{ok, <anno>Dir</anno>}</c> or @@ -547,7 +547,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="list_dir" arity="1"/> + <name name="list_dir" arity="1" since=""/> <fsummary>List files in a directory.</fsummary> <desc> <p>Lists all files in a directory, <em>except</em> files @@ -578,7 +578,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="list_dir_all" arity="1"/> + <name name="list_dir_all" arity="1" since="OTP R16B"/> <fsummary>List all files in a directory.</fsummary> <desc> <p><marker id="list_dir_all"/>Lists all the files in a directory, @@ -603,7 +603,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="make_dir" arity="1"/> + <name name="make_dir" arity="1" since=""/> <fsummary>Make a directory.</fsummary> <desc> <p>Tries to create directory <c><anno>Dir</anno></c>. Missing parent @@ -637,7 +637,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="make_link" arity="2"/> + <name name="make_link" arity="2" since=""/> <fsummary>Make a hard link to a file.</fsummary> <desc> <p>Makes a hard link from <c><anno>Existing</anno></c> to @@ -666,7 +666,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="make_symlink" arity="2"/> + <name name="make_symlink" arity="2" since=""/> <fsummary>Make a symbolic link to a file or directory.</fsummary> <desc> <p>Creates a symbolic link <c><anno>New</anno></c> to @@ -702,7 +702,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="native_name_encoding" arity="0"/> + <name name="native_name_encoding" arity="0" since="OTP R14B01"/> <fsummary>Return the configured filename encoding of the VM.</fsummary> <desc> <p><marker id="native_name_encoding"/>Returns @@ -714,7 +714,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="open" arity="2"/> + <name name="open" arity="2" since=""/> <fsummary>Open a file.</fsummary> <desc> <p>Opens file <c><anno>File</anno></c> in the mode determined @@ -997,7 +997,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_consult" arity="2"/> + <name name="path_consult" arity="2" since=""/> <fsummary>Read Erlang terms from a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1039,7 +1039,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_eval" arity="2"/> + <name name="path_eval" arity="2" since=""/> <fsummary>Evaluate Erlang expressions in a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1085,7 +1085,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_open" arity="3"/> + <name name="path_open" arity="3" since=""/> <fsummary>Open a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1114,7 +1114,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_script" arity="2"/> + <name name="path_script" arity="2" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>Searches the path <c><anno>Path</anno></c> (a list of directory @@ -1158,7 +1158,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="path_script" arity="3"/> + <name name="path_script" arity="3" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>The same as <c>path_script/2</c> but the variable bindings @@ -1168,7 +1168,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pid2name" arity="1"/> + <name name="pid2name" arity="1" since=""/> <fsummary>Return the name of the file handled by a pid.</fsummary> <desc> <p>If <c><anno>Pid</anno></c> is an I/O device, that is, a pid returned from @@ -1193,7 +1193,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="position" arity="2"/> + <name name="position" arity="2" since=""/> <fsummary>Set position in a file.</fsummary> <desc> <p>Sets the position of the file referenced by <c><anno>IoDevice</anno></c> @@ -1245,7 +1245,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pread" arity="2"/> + <name name="pread" arity="2" since=""/> <fsummary>Read from a file at certain positions.</fsummary> <desc> <p>Performs a sequence of <c>pread/3</c> in one operation, @@ -1263,7 +1263,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pread" arity="3"/> + <name name="pread" arity="3" since=""/> <fsummary>Read from a file at a certain position.</fsummary> <desc> <p>Combines <c>position/2</c> and <c>read/2</c> in one @@ -1283,7 +1283,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pwrite" arity="2"/> + <name name="pwrite" arity="2" since=""/> <fsummary>Write to a file at certain positions.</fsummary> <desc> <p>Performs a sequence of <c>pwrite/3</c> in one operation, @@ -1298,7 +1298,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="pwrite" arity="3"/> + <name name="pwrite" arity="3" since=""/> <fsummary>Write to a file at a certain position.</fsummary> <desc> <p>Combines <c>position/2</c> and <c>write/2</c> in one @@ -1317,7 +1317,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read" arity="2"/> + <name name="read" arity="2" since=""/> <fsummary>Read from a file.</fsummary> <desc> <p>Reads <c><anno>Number</anno></c> bytes/characters from the file @@ -1371,7 +1371,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_file" arity="1"/> + <name name="read_file" arity="1" since=""/> <fsummary>Read a file.</fsummary> <desc> <p>Returns <c>{ok, <anno>Binary</anno>}</c>, where @@ -1407,8 +1407,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_file_info" arity="1"/> - <name name="read_file_info" arity="2"/> + <name name="read_file_info" arity="1" since=""/> + <name name="read_file_info" arity="2" since="OTP R15B"/> <fsummary>Retrieve information about a file.</fsummary> <desc> <p>Retrieves information about a file. Returns @@ -1562,7 +1562,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_line" arity="1"/> + <name name="read_line" arity="1" since=""/> <fsummary>Read a line from a file.</fsummary> <desc> <p>Reads a line of bytes/characters from the file referenced by @@ -1619,7 +1619,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_link" arity="1"/> + <name name="read_link" arity="1" since=""/> <fsummary>See what a link is pointing to.</fsummary> <desc> <p><marker id="read_link_all"/>Returns @@ -1649,7 +1649,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_link_all" arity="1"/> + <name name="read_link_all" arity="1" since="OTP R16B"/> <fsummary>See what a link is pointing to.</fsummary> <desc> <p>Returns <c>{ok, <anno>Filename</anno>}</c> if @@ -1677,8 +1677,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="read_link_info" arity="1"/> - <name name="read_link_info" arity="2"/> + <name name="read_link_info" arity="1" since=""/> + <name name="read_link_info" arity="2" since="OTP R15B"/> <fsummary>Retrieve information about a link or file.</fsummary> <desc> <p>Works like @@ -1699,7 +1699,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="rename" arity="2"/> + <name name="rename" arity="2" since=""/> <fsummary>Rename a file.</fsummary> <desc> <p>Tries to rename the file <c><anno>Source</anno></c> to @@ -1762,7 +1762,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="script" arity="1"/> + <name name="script" arity="1" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>Reads and evaluates Erlang expressions, separated by '.' (or @@ -1797,7 +1797,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="script" arity="2"/> + <name name="script" arity="2" since=""/> <fsummary>Evaluate and return the value of Erlang expressions in a file.</fsummary> <desc> <p>The same as <c>script/1</c> but the variable bindings @@ -1807,7 +1807,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="sendfile" arity="2"/> + <name name="sendfile" arity="2" since="OTP R15B"/> <fsummary>Send a file to a socket.</fsummary> <desc> <p>Sends the file <c>Filename</c> to <c>Socket</c>. @@ -1816,7 +1816,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="sendfile" arity="5"/> + <name name="sendfile" arity="5" since="OTP R15B"/> <fsummary>Send a file to a socket.</fsummary> <type name="sendfile_option"/> <desc> @@ -1843,7 +1843,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="set_cwd" arity="1"/> + <name name="set_cwd" arity="1" since=""/> <fsummary>Set the current working directory.</fsummary> <desc> <p>Sets the current working directory of the file server to @@ -1890,7 +1890,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="sync" arity="1"/> + <name name="sync" arity="1" since=""/> <fsummary>Synchronize the in-memory state of a file with that on the physical medium.</fsummary> <desc> <p>Ensures that any buffers kept by the operating system @@ -1906,7 +1906,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="truncate" arity="1"/> + <name name="truncate" arity="1" since=""/> <fsummary>Truncate a file.</fsummary> <desc> <p>Truncates the file referenced by <c><anno>IoDevice</anno></c> at @@ -1915,7 +1915,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write" arity="2"/> + <name name="write" arity="2" since=""/> <fsummary>Write to a file.</fsummary> <desc> <p>Writes <c><anno>Bytes</anno></c> to the file referenced by @@ -1941,7 +1941,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write_file" arity="2"/> + <name name="write_file" arity="2" since=""/> <fsummary>Write a file.</fsummary> <desc> <p>Writes the contents of the <c>iodata</c> term <c><anno>Bytes</anno></c> @@ -1978,7 +1978,7 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write_file" arity="3"/> + <name name="write_file" arity="3" since=""/> <fsummary>Write a file.</fsummary> <desc> <p>Same as <c>write_file/2</c>, but takes a third argument @@ -1989,8 +1989,8 @@ f.txt: {person, "kalle", 25}. </desc> </func> <func> - <name name="write_file_info" arity="2"/> - <name name="write_file_info" arity="3"/> + <name name="write_file_info" arity="2" since=""/> + <name name="write_file_info" arity="3" since="OTP R15B"/> <fsummary>Change file information.</fsummary> <desc> <p>Changes file information. Returns <c>ok</c> if successful, diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml index 1e08b25f66..f70d6c24db 100644 --- a/lib/kernel/doc/src/gen_sctp.xml +++ b/lib/kernel/doc/src/gen_sctp.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>gen_sctp.xml</file> </header> - <module>gen_sctp</module> + <module since="">gen_sctp</module> <modulesummary>Functions for communicating with sockets using the SCTP protocol.</modulesummary> <description> @@ -100,7 +100,7 @@ <funcs> <func> - <name name="abort" arity="2"/> + <name name="abort" arity="2" since=""/> <fsummary>Abnormally terminate the association specified by <c>Assoc</c>, without flushing of unsent data.</fsummary> <desc> @@ -113,7 +113,7 @@ </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close the socket and all associations on it.</fsummary> <desc> <p>Closes the socket and all associations on it. The unsent @@ -128,7 +128,7 @@ </func> <func> - <name name="connect" arity="4"/> + <name name="connect" arity="4" since=""/> <fsummary>Same as <c>connect(Socket, Addr, Port, Opts, infinity)</c>.</fsummary> <desc> <p>Same as <c>connect(<anno>Socket</anno>, <anno>Addr</anno>, @@ -137,7 +137,7 @@ </func> <func> - <name name="connect" arity="5"/> + <name name="connect" arity="5" since=""/> <fsummary>Establish a new association for socket <c>Socket</c>, with a peer (SCTP server socket).</fsummary> <desc> @@ -213,7 +213,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="connect_init" arity="4"/> + <name name="connect_init" arity="4" since="OTP R13B04"/> <fsummary>Same as <c>connect_init(Socket, Addr, Port, Opts, infinity)</c>..</fsummary> <desc> <p>Same as <c>connect_init(<anno>Socket</anno>, <anno>Addr</anno>, @@ -222,7 +222,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="connect_init" arity="5"/> + <name name="connect_init" arity="5" since="OTP R13B04"/> <fsummary>Initiate a new association for socket <c>Socket</c>, with a peer (SCTP server socket).</fsummary> <desc> @@ -248,7 +248,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="controlling_process" arity="2"/> + <name name="controlling_process" arity="2" since=""/> <fsummary>Assign a new controlling process pid to the socket.</fsummary> <desc> <p>Assigns a new controlling process <c><anno>Pid</anno></c> to @@ -259,7 +259,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="eof" arity="2"/> + <name name="eof" arity="2" since=""/> <fsummary>Gracefully terminate the association specified by <c>Assoc</c>, with flushing of all unsent data.</fsummary> <desc> @@ -272,7 +272,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="error_string" arity="1"/> + <name name="error_string" arity="1" since=""/> <fsummary>Translate an SCTP error number into a string.</fsummary> <desc> <p>Translates an SCTP error number from, for example, @@ -283,8 +283,8 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="listen" arity="2" clause_i="1"/> - <name name="listen" arity="2" clause_i="2"/> + <name name="listen" arity="2" clause_i="1" since=""/> + <name name="listen" arity="2" clause_i="2" since="OTP R15B"/> <fsummary>Set up a socket to listen.</fsummary> <desc> <p>Sets up a socket to listen on the IP address and port number @@ -300,10 +300,10 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="open" arity="0"/> - <name name="open" arity="1" clause_i="1"/> - <name name="open" arity="1" clause_i="2"/> - <name name="open" arity="2"/> + <name name="open" arity="0" since=""/> + <name name="open" arity="1" clause_i="1" since=""/> + <name name="open" arity="1" clause_i="2" since=""/> + <name name="open" arity="2" since=""/> <fsummary>Create an SCTP socket and binds it to local addresses.</fsummary> <desc> <p>Creates an SCTP socket and binds it to the local addresses @@ -366,7 +366,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="peeloff" arity="2"/> + <name name="peeloff" arity="2" since="OTP R15B"/> <fsummary>Peel off a type <c>stream</c> socket from a type <c>seqpacket</c> one.</fsummary> <desc> @@ -387,8 +387,8 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="recv" arity="1"/> - <name name="recv" arity="2"/> + <name name="recv" arity="1" since=""/> + <name name="recv" arity="2" since=""/> <fsummary>Receive a message from a socket.</fsummary> <desc> <p>Receives the <c><anno>Data</anno></c> message from any association @@ -532,7 +532,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="send" arity="3"/> + <name name="send" arity="3" since=""/> <fsummary>Send a message using an <c>#sctp_sndrcvinfo{}</c>record.</fsummary> <desc> <p>Sends the <c><anno>Data</anno></c> message with all sending @@ -547,7 +547,7 @@ connect(Socket, Ip, Port>, </func> <func> - <name name="send" arity="4"/> + <name name="send" arity="4" since=""/> <fsummary>Send a message over an existing association and specified stream.</fsummary> <desc> diff --git a/lib/kernel/doc/src/gen_tcp.xml b/lib/kernel/doc/src/gen_tcp.xml index 24d63693fd..fc16473393 100644 --- a/lib/kernel/doc/src/gen_tcp.xml +++ b/lib/kernel/doc/src/gen_tcp.xml @@ -27,7 +27,7 @@ <date>1997-10-24</date> <rev>A</rev> </header> - <module>gen_tcp</module> + <module since="">gen_tcp</module> <modulesummary>Interface to TCP/IP sockets.</modulesummary> <description> <p>This module provides functions for communicating @@ -116,8 +116,8 @@ do_recv(Sock, Bs) -> <funcs> <func> - <name name="accept" arity="1"/> - <name name="accept" arity="2"/> + <name name="accept" arity="1" since=""/> + <name name="accept" arity="2" since=""/> <fsummary>Accept an incoming connection request on a listening socket.</fsummary> <type_desc variable="ListenSocket">Returned by <seealso marker="#listen/2"><c>listen/2</c></seealso>. @@ -163,7 +163,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a TCP socket.</fsummary> <desc> <p>Closes a TCP socket.</p> @@ -188,8 +188,8 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="connect" arity="3"/> - <name name="connect" arity="4"/> + <name name="connect" arity="3" since=""/> + <name name="connect" arity="4" since=""/> <fsummary>Connect to a TCP port.</fsummary> <desc> <p>Connects to a server on TCP port <c><anno>Port</anno></c> on the host @@ -268,7 +268,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="controlling_process" arity="2"/> + <name name="controlling_process" arity="2" since=""/> <fsummary>Change controlling process of a socket.</fsummary> <desc> <p>Assigns a new controlling process <c><anno>Pid</anno></c> to @@ -292,7 +292,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="listen" arity="2"/> + <name name="listen" arity="2" since=""/> <fsummary>Set up a socket to listen on a port.</fsummary> <desc> <p>Sets up a socket to listen on port <c><anno>Port</anno></c> on @@ -349,8 +349,8 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="recv" arity="2"/> - <name name="recv" arity="3"/> + <name name="recv" arity="2" since=""/> + <name name="recv" arity="3" since=""/> <fsummary>Receive a packet from a passive socket.</fsummary> <type_desc variable="HttpPacket">See the description of <c>HttpPacket</c> in @@ -375,7 +375,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="send" arity="2"/> + <name name="send" arity="2" since=""/> <fsummary>Send a packet.</fsummary> <desc> <p>Sends a packet on a socket.</p> @@ -386,7 +386,7 @@ do_recv(Sock, Bs) -> </func> <func> - <name name="shutdown" arity="2"/> + <name name="shutdown" arity="2" since=""/> <fsummary>Asynchronously close a socket.</fsummary> <desc> <p>Closes a socket in one or two directions.</p> diff --git a/lib/kernel/doc/src/gen_udp.xml b/lib/kernel/doc/src/gen_udp.xml index 840ca3c188..d20fc1fdfd 100644 --- a/lib/kernel/doc/src/gen_udp.xml +++ b/lib/kernel/doc/src/gen_udp.xml @@ -28,7 +28,7 @@ <date>1997-12-03</date> <rev>A</rev> </header> - <module>gen_udp</module> + <module since="">gen_udp</module> <modulesummary>Interface to UDP sockets.</modulesummary> <description> <p>This module provides functions for communicating @@ -53,7 +53,7 @@ <funcs> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a UDP socket.</fsummary> <desc> <p>Closes a UDP socket.</p> @@ -61,7 +61,7 @@ </func> <func> - <name name="controlling_process" arity="2"/> + <name name="controlling_process" arity="2" since=""/> <fsummary>Change controlling process of a socket.</fsummary> <desc> <p>Assigns a new controlling process <c><anno>Pid</anno></c> to @@ -77,8 +77,8 @@ </func> <func> - <name name="open" arity="1"/> - <name name="open" arity="2"/> + <name name="open" arity="1" since=""/> + <name name="open" arity="2" since=""/> <fsummary>Associate a UDP port number with the process calling it.</fsummary> <desc> <p>Associates a UDP port number (<c><anno>Port</anno></c>) with the @@ -189,8 +189,8 @@ </func> <func> - <name name="recv" arity="2"/> - <name name="recv" arity="3"/> + <name name="recv" arity="2" since=""/> + <name name="recv" arity="3" since=""/> <fsummary>Receive a packet from a passive socket.</fsummary> <desc> <p> @@ -213,7 +213,7 @@ </func> <func> - <name name="send" arity="4"/> + <name name="send" arity="4" since=""/> <fsummary>Send a packet.</fsummary> <desc> <p> diff --git a/lib/kernel/doc/src/global.xml b/lib/kernel/doc/src/global.xml index 4442741f54..dfe71de5ce 100644 --- a/lib/kernel/doc/src/global.xml +++ b/lib/kernel/doc/src/global.xml @@ -28,7 +28,7 @@ <date>1997-11-17</date> <rev></rev> </header> - <module>global</module> + <module since="">global</module> <modulesummary>A global name registration facility.</modulesummary> <description> <p>This module consists of the following services:</p> @@ -100,8 +100,8 @@ <funcs> <func> - <name name="del_lock" arity="1"/> - <name name="del_lock" arity="2"/> + <name name="del_lock" arity="1" since=""/> + <name name="del_lock" arity="2" since=""/> <fsummary>Delete a lock.</fsummary> <desc> <p>Deletes the lock <c><anno>Id</anno></c> synchronously.</p> @@ -109,7 +109,7 @@ </func> <func> - <name name="notify_all_name" arity="3"/> + <name name="notify_all_name" arity="3" since=""/> <fsummary>Name resolving function that notifies both pids.</fsummary> <desc> <p>Can be used as a name resolving function for @@ -123,7 +123,7 @@ </func> <func> - <name name="random_exit_name" arity="3"/> + <name name="random_exit_name" arity="3" since=""/> <fsummary>Name resolving function that kills one pid.</fsummary> <desc> <p>Can be used as a name resolving function for @@ -136,7 +136,7 @@ </func> <func> - <name name="random_notify_name" arity="3"/> + <name name="random_notify_name" arity="3" since=""/> <fsummary>Name resolving function that notifies one pid.</fsummary> <desc> <p>Can be used as a name resolving function for @@ -150,8 +150,8 @@ </func> <func> - <name name="re_register_name" arity="2"/> - <name name="re_register_name" arity="3"/> + <name name="re_register_name" arity="2" since=""/> + <name name="re_register_name" arity="3" since=""/> <fsummary>Atomically re-register a name.</fsummary> <type name="method"/> <type_desc name="method">{<c>Module</c>, <c>Function</c>} @@ -167,8 +167,8 @@ </func> <func> - <name name="register_name" arity="2"/> - <name name="register_name" arity="3"/> + <name name="register_name" arity="2" since=""/> + <name name="register_name" arity="3" since=""/> <fsummary>Globally register a name for a pid.</fsummary> <type name="method"/> <type_desc name="method">{<c>Module</c>, <c>Function</c>} is also @@ -221,7 +221,7 @@ </func> <func> - <name name="registered_names" arity="0"/> + <name name="registered_names" arity="0" since=""/> <fsummary>All globally registered names.</fsummary> <desc> <p>Returns a list of all globally registered names.</p> @@ -229,7 +229,7 @@ </func> <func> - <name name="send" arity="2"/> + <name name="send" arity="2" since=""/> <fsummary>Send a message to a globally registered pid.</fsummary> <desc> <p>Sends message <c><anno>Msg</anno></c> to the pid globally registered @@ -241,9 +241,9 @@ </func> <func> - <name name="set_lock" arity="1"/> - <name name="set_lock" arity="2"/> - <name name="set_lock" arity="3"/> + <name name="set_lock" arity="1" since=""/> + <name name="set_lock" arity="2" since=""/> + <name name="set_lock" arity="3" since=""/> <fsummary>Set a lock on the specified nodes.</fsummary> <type name="id"/> <type name="retries"/> @@ -287,7 +287,7 @@ </func> <func> - <name name="sync" arity="0"/> + <name name="sync" arity="0" since=""/> <fsummary>Synchronize the global name server.</fsummary> <desc> <p>Synchronizes the global name server with all nodes known to @@ -302,9 +302,9 @@ </func> <func> - <name name="trans" arity="2"/> - <name name="trans" arity="3"/> - <name name="trans" arity="4"/> + <name name="trans" arity="2" since=""/> + <name name="trans" arity="3" since=""/> + <name name="trans" arity="4" since=""/> <fsummary>Micro transaction facility.</fsummary> <type name="retries"/> <type name="trans_fun"/> @@ -322,7 +322,7 @@ </func> <func> - <name name="unregister_name" arity="1"/> + <name name="unregister_name" arity="1" since=""/> <fsummary>Remove a globally registered name for a pid.</fsummary> <desc> <p>Removes the globally registered name <c><anno>Name</anno></c> from @@ -331,7 +331,7 @@ </func> <func> - <name name="whereis_name" arity="1"/> + <name name="whereis_name" arity="1" since=""/> <fsummary>Get the pid with a specified globally registered name.</fsummary> <desc> <p>Returns the pid with the globally registered name diff --git a/lib/kernel/doc/src/global_group.xml b/lib/kernel/doc/src/global_group.xml index 8f947b9adf..74d15cd476 100644 --- a/lib/kernel/doc/src/global_group.xml +++ b/lib/kernel/doc/src/global_group.xml @@ -28,7 +28,7 @@ <date>1998-12-18</date> <rev>B</rev> </header> - <module>global_group</module> + <module since="">global_group</module> <modulesummary>Grouping nodes to global name registration groups.</modulesummary> <description> <p>This module makes it possible to partition the nodes of a @@ -105,7 +105,7 @@ <funcs> <func> - <name name="global_groups" arity="0"/> + <name name="global_groups" arity="0" since=""/> <fsummary>Return the global group names.</fsummary> <desc> <p>Returns a tuple containing the name of the global group that @@ -116,7 +116,7 @@ </func> <func> - <name name="info" arity="0"/> + <name name="info" arity="0" since=""/> <fsummary>Information about global groups.</fsummary> <type name="info_item"/> <type name="sync_state"/> @@ -173,7 +173,7 @@ </func> <func> - <name name="monitor_nodes" arity="1"/> + <name name="monitor_nodes" arity="1" since=""/> <fsummary>Subscribe to node status changes.</fsummary> <desc> <p>Depending on <c><anno>Flag</anno></c>, the calling process @@ -187,7 +187,7 @@ </func> <func> - <name name="own_nodes" arity="0"/> + <name name="own_nodes" arity="0" since=""/> <fsummary>Return the group nodes.</fsummary> <desc> <p>Returns the names of all group nodes, regardless of their @@ -196,7 +196,7 @@ </func> <func> - <name name="registered_names" arity="1"/> + <name name="registered_names" arity="1" since=""/> <fsummary>Return globally registered names.</fsummary> <desc> <p>Returns a list of all names that are globally registered @@ -205,8 +205,8 @@ </func> <func> - <name name="send" arity="2"/> - <name name="send" arity="3"/> + <name name="send" arity="2" since=""/> + <name name="send" arity="3" since=""/> <fsummary>Send a message to a globally registered pid.</fsummary> <desc> <p>Searches for <c><anno>Name</anno></c>, globally registered on @@ -224,7 +224,7 @@ </func> <func> - <name name="sync" arity="0"/> + <name name="sync" arity="0" since=""/> <fsummary>Synchronize the group nodes.</fsummary> <desc> <p>Synchronizes the group nodes, that is, the global name @@ -242,8 +242,8 @@ </func> <func> - <name name="whereis_name" arity="1"/> - <name name="whereis_name" arity="2"/> + <name name="whereis_name" arity="1" since=""/> + <name name="whereis_name" arity="2" since=""/> <fsummary>Get the pid with a specified globally registered name.</fsummary> <desc> <p>Searches for <c><anno>Name</anno></c>, globally registered on diff --git a/lib/kernel/doc/src/heart.xml b/lib/kernel/doc/src/heart.xml index ad1a2ffeb9..4243b1ffe8 100644 --- a/lib/kernel/doc/src/heart.xml +++ b/lib/kernel/doc/src/heart.xml @@ -28,7 +28,7 @@ <date>1998-01-28</date> <rev>A</rev> </header> - <module>heart</module> + <module since="">heart</module> <modulesummary>Heartbeat monitoring of an Erlang runtime system.</modulesummary> <description> <p>This modules contains the interface to the <c>heart</c> process. @@ -119,7 +119,7 @@ <funcs> <func> - <name name="set_cmd" arity="1"/> + <name name="set_cmd" arity="1" since=""/> <fsummary>Set a temporary reboot command.</fsummary> <desc> <p>Sets a temporary reboot command. This command is used if @@ -136,7 +136,7 @@ </func> <func> - <name name="clear_cmd" arity="0"/> + <name name="clear_cmd" arity="0" since=""/> <fsummary>Clear the temporary boot command.</fsummary> <desc> <p>Clears the temporary boot command. If the system terminates, @@ -145,7 +145,7 @@ </func> <func> - <name name="get_cmd" arity="0"/> + <name name="get_cmd" arity="0" since=""/> <fsummary>Get the temporary reboot command.</fsummary> <desc> <p>Gets the temporary reboot command. If the command is cleared, @@ -154,7 +154,7 @@ </func> <func> - <name name="set_callback" arity="2"/> + <name name="set_callback" arity="2" since="OTP 18.3"/> <fsummary>Set a validation callback</fsummary> <desc> <p> This validation callback will be executed before any @@ -166,14 +166,14 @@ </desc> </func> <func> - <name name="clear_callback" arity="0"/> + <name name="clear_callback" arity="0" since="OTP 18.3"/> <fsummary>Clear the validation callback</fsummary> <desc> <p>Removes the validation callback call before heartbeats.</p> </desc> </func> <func> - <name name="get_callback" arity="0"/> + <name name="get_callback" arity="0" since="OTP 18.3"/> <fsummary>Get the validation callback</fsummary> <desc> <p>Get the validation callback. If the callback is cleared, <c>none</c> will be returned.</p> @@ -181,7 +181,7 @@ </func> <func> - <name name="set_options" arity="1"/> + <name name="set_options" arity="1" since="OTP 18.3"/> <fsummary>Set a list of options</fsummary> <desc> <p> Valid options <c>set_options</c> are: </p> @@ -199,7 +199,7 @@ </desc> </func> <func> - <name name="get_options" arity="0"/> + <name name="get_options" arity="0" since="OTP 18.3"/> <fsummary>Get the temporary reboot command</fsummary> <desc> <p>Returns <c>{ok, Options}</c> where <c>Options</c> is a list of current options enabled for heart. diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index 87b08e4e36..709ba8e8fd 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -28,7 +28,7 @@ <date>1998-02-04</date> <rev>A</rev> </header> - <module>inet</module> + <module since="">inet</module> <modulesummary>Access to TCP/IP protocols.</modulesummary> <description> <p>This module provides access to TCP/IP protocols.</p> @@ -298,7 +298,7 @@ fe80::204:acff:fe17:bf38 <funcs> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a socket of any type.</fsummary> <desc> <p>Closes a socket of any type.</p> @@ -306,7 +306,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return a descriptive string for an error reason.</fsummary> <desc> <p>Returns a diagnostic error string. For possible POSIX values and @@ -316,7 +316,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="get_rc" arity="0"/> + <name name="get_rc" arity="0" since=""/> <fsummary>Return a list of IP configuration parameters.</fsummary> <desc> <p> @@ -335,7 +335,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getaddr" arity="2"/> + <name name="getaddr" arity="2" since=""/> <fsummary>Return the IP address for a host.</fsummary> <desc> <p>Returns the IP address for <c><anno>Host</anno></c> as a tuple of @@ -345,7 +345,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getaddrs" arity="2"/> + <name name="getaddrs" arity="2" since=""/> <fsummary>Return the IP addresses for a host.</fsummary> <desc> <p>Returns a list of all IP addresses for <c><anno>Host</anno></c>. @@ -355,7 +355,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostbyaddr" arity="1"/> + <name name="gethostbyaddr" arity="1" since=""/> <fsummary>Return a hostent record for the host with the specified address.</fsummary> <desc> @@ -364,7 +364,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostbyname" arity="1"/> + <name name="gethostbyname" arity="1" since=""/> <fsummary>Return a hostent record for the host with the specified name. </fsummary> <desc> @@ -376,7 +376,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostbyname" arity="2"/> + <name name="gethostbyname" arity="2" since=""/> <fsummary>Return a hostent record for the host with the specified name. </fsummary> <desc> @@ -386,7 +386,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="gethostname" arity="0"/> + <name name="gethostname" arity="0" since=""/> <fsummary>Return the local hostname.</fsummary> <desc> <p>Returns the local hostname. Never fails.</p> @@ -394,7 +394,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getifaddrs" arity="0"/> + <name name="getifaddrs" arity="0" since="OTP R14B01"/> <fsummary>Return a list of interfaces and their addresses.</fsummary> <desc> <p> @@ -416,7 +416,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name>getifaddrs(Opts) -> + <name since="OTP 21.2">getifaddrs(Opts) -> {ok, [{Ifname, Ifopts}]} | {error, Posix} </name> <fsummary>Return a list of interfaces and their addresses.</fsummary> @@ -459,7 +459,7 @@ fe80::204:acff:fe17:bf38 </func> <func> - <name name="getopts" arity="2"/> + <name name="getopts" arity="2" since=""/> <fsummary>Get one or more options for a socket.</fsummary> <desc> <p>Gets one or more options for a socket. For a list of available @@ -529,8 +529,8 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="getstat" arity="1"/> - <name name="getstat" arity="2"/> + <name name="getstat" arity="1" since=""/> + <name name="getstat" arity="2" since=""/> <fsummary>Get one or more statistic options for a socket.</fsummary> <type name="stat_option"/> <desc> @@ -586,9 +586,9 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="i" arity="0" /> - <name name="i" arity="1" /> - <name name="i" arity="2" /> + <name name="i" arity="0" since="OTP 21.0"/> + <name name="i" arity="1" since="OTP 21.0"/> + <name name="i" arity="2" since="OTP 21.0"/> <fsummary>Displays information and statistics about sockets on the terminal</fsummary> <desc> <p> @@ -641,7 +641,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="ntoa" arity="1" /> + <name name="ntoa" arity="1" since="OTP R16B02"/> <fsummary>Convert IPv6/IPV4 address to ASCII.</fsummary> <desc> <p>Parses an @@ -651,7 +651,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_address" arity="1" /> + <name name="parse_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 or IPv6 address.</fsummary> <desc> <p>Parses an IPv4 or IPv6 address string and returns an @@ -662,7 +662,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv4_address" arity="1" /> + <name name="parse_ipv4_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 address.</fsummary> <desc> <p>Parses an IPv4 address string and returns an @@ -672,7 +672,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv4strict_address" arity="1" /> + <name name="parse_ipv4strict_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 address strict.</fsummary> <desc> <p>Parses an IPv4 address string containing four fields, that is, @@ -683,7 +683,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv6_address" arity="1" /> + <name name="parse_ipv6_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv6 address.</fsummary> <desc> <p>Parses an IPv6 address string and returns an @@ -694,7 +694,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_ipv6strict_address" arity="1" /> + <name name="parse_ipv6strict_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv6 address strict.</fsummary> <desc> <p>Parses an IPv6 address string and returns an @@ -704,7 +704,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="ipv4_mapped_ipv6_address" arity="1" /> + <name name="ipv4_mapped_ipv6_address" arity="1" since="OTP 21.0"/> <fsummary>Convert to and from IPv4-mapped IPv6 address.</fsummary> <desc> <p> @@ -717,7 +717,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="parse_strict_address" arity="1" /> + <name name="parse_strict_address" arity="1" since="OTP R16B"/> <fsummary>Parse an IPv4 or IPv6 address strict.</fsummary> <desc> <p>Parses an IPv4 or IPv6 address string and returns an @@ -728,7 +728,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="peername" arity="1"/> + <name name="peername" arity="1" since=""/> <fsummary>Return the address and port for the other end of a connection. </fsummary> <desc> @@ -741,7 +741,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="peernames" arity="1"/> + <name name="peernames" arity="1" since="OTP R16B03"/> <fsummary>Return all address/port numbers for the other end of a connection.</fsummary> <desc> @@ -755,7 +755,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="peernames" arity="2"/> + <name name="peernames" arity="2" since="OTP R16B03"/> <fsummary>Return all address/port numbers for the other end of a connection.</fsummary> <desc> @@ -774,7 +774,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="port" arity="1"/> + <name name="port" arity="1" since=""/> <fsummary>Return the local port number for a socket.</fsummary> <desc> <p>Returns the local port number for a socket.</p> @@ -782,7 +782,7 @@ get_tcpi_sacked(Sock) -> </func> <func> - <name name="setopts" arity="2"/> + <name name="setopts" arity="2" since=""/> <fsummary>Set one or more options for a socket.</fsummary> <desc> <p>Sets one or more options for a socket.</p> @@ -1007,13 +1007,34 @@ get_tcpi_sacked(Sock) -> <marker id="option-linger"></marker> </item> <tag><c>{linger, {true|false, Seconds}}</c></tag> - <item> + <item> <p>Determines the time-out, in seconds, for flushing unsent data - in the <c>close/1</c> socket call. If the first component of - the value tuple is <c>false</c>, the second is ignored. This - means that <c>close/1</c> returns immediately, not waiting - for data to be flushed. Otherwise, the second component is - the flushing time-out, in seconds.</p> + in the <c>close/1</c> socket call. </p> + <p>The first component is if linger is enabled, the second component + is the flushing time-out, in seconds. There are 3 alternatives:</p> + <taglist> + <tag><c>{false, _}</c></tag> + <item> + <p>close/1 or shutdown/2 returns immediately, + not waiting for data to be flushed, with closing + happening in the background.</p> + </item> + <tag><c>{true, 0}</c></tag> + <item> + <p>Aborts the connection when it is closed. + Discards any data still remaining in the send buffers + and sends RST to the peer.</p> + <p>This avoids TCP's TIME_WAIT state, but leaves open + the possibility that another "incarnation" of this connection + being created.</p> + </item> + <tag><c>{true, Time} when Time > 0</c></tag> + <item> + <p>close/1 or shutdown/2 will not return until + all queued messages for the socket have been successfully + sent or the linger timeout (Time) has been reached.</p> + </item> + </taglist> </item> <tag><c>{low_msgq_watermark, Size}</c></tag> <item> @@ -1486,7 +1507,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> </func> <func> - <name name="sockname" arity="1"/> + <name name="sockname" arity="1" since=""/> <fsummary>Return the local address and port number for a socket. </fsummary> <desc> @@ -1499,7 +1520,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> </func> <func> - <name name="socknames" arity="1"/> + <name name="socknames" arity="1" since="OTP R16B03"/> <fsummary>Return all local address/port numbers for a socket.</fsummary> <desc> <p>Equivalent to @@ -1509,7 +1530,7 @@ inet:setopts(Sock,[{raw,6,8,<<30:32/native>>}]),]]></code> </func> <func> - <name name="socknames" arity="2"/> + <name name="socknames" arity="2" since="OTP R16B03"/> <fsummary>Return all local address/port numbers for a socket.</fsummary> <desc> <p>Returns a list of all local address/port number pairs for a socket diff --git a/lib/kernel/doc/src/inet_res.xml b/lib/kernel/doc/src/inet_res.xml index 351d86a93a..1904e371f7 100644 --- a/lib/kernel/doc/src/inet_res.xml +++ b/lib/kernel/doc/src/inet_res.xml @@ -28,7 +28,7 @@ <date>2009-09-11</date> <rev>A</rev> </header> - <module>inet_res</module> + <module since="">inet_res</module> <modulesummary>A rudimentary DNS client.</modulesummary> <description> <p>This module performs DNS name resolving to recursive name servers.</p> @@ -185,8 +185,8 @@ inet_dns:record_type(_) -> undefined.</pre> <funcs> <func> - <name name="getbyname" arity="2"/> - <name name="getbyname" arity="3"/> + <name name="getbyname" arity="2" since=""/> + <name name="getbyname" arity="3" since=""/> <fsummary>Resolve a DNS record of the specified type for the specified host.</fsummary> <desc> @@ -205,8 +205,8 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="gethostbyaddr" arity="1"/> - <name name="gethostbyaddr" arity="2"/> + <name name="gethostbyaddr" arity="1" since=""/> + <name name="gethostbyaddr" arity="2" since=""/> <fsummary>Return a hostent record for the host with the specified address.</fsummary> <desc> @@ -217,9 +217,9 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="gethostbyname" arity="1"/> - <name name="gethostbyname" arity="2"/> - <name name="gethostbyname" arity="3"/> + <name name="gethostbyname" arity="1" since=""/> + <name name="gethostbyname" arity="2" since=""/> + <name name="gethostbyname" arity="3" since=""/> <fsummary>Return a hostent record for the host with the specified name. </fsummary> <desc> @@ -235,9 +235,9 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="lookup" arity="3"/> - <name name="lookup" arity="4"/> - <name name="lookup" arity="5"/> + <name name="lookup" arity="3" since=""/> + <name name="lookup" arity="4" since=""/> + <name name="lookup" arity="5" since=""/> <fsummary>Resolve the DNS data for the record of the specified type and class for the specified name.</fsummary> <desc> @@ -257,9 +257,9 @@ inet_dns:record_type(_) -> undefined.</pre> </func> <func> - <name name="resolve" arity="3"/> - <name name="resolve" arity="4"/> - <name name="resolve" arity="5"/> + <name name="resolve" arity="3" since=""/> + <name name="resolve" arity="4" since=""/> + <name name="resolve" arity="5" since=""/> <fsummary>Resolve a DNS record of the specified type and class for the specified name.</fsummary> <desc> @@ -326,9 +326,9 @@ example_lookup(Name, Class, Type) -> <funcs> <func> - <name name="nslookup" arity="3"/> - <name name="nslookup" arity="4" clause_i="1"/> - <name name="nslookup" arity="4" clause_i="2"/> + <name name="nslookup" arity="3" since=""/> + <name name="nslookup" arity="4" clause_i="1" since=""/> + <name name="nslookup" arity="4" clause_i="2" since=""/> <fsummary>Resolve a DNS record of the specified type and class for the specified name.</fsummary> <type variable="Name"/> @@ -344,8 +344,8 @@ example_lookup(Name, Class, Type) -> </func> <func> - <name name="nnslookup" arity="4"/> - <name name="nnslookup" arity="5"/> + <name name="nnslookup" arity="4" since=""/> + <name name="nnslookup" arity="5" since=""/> <fsummary>Resolve a DNS record of the specified type and class for the specified name.</fsummary> <desc> diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml index 2bcf137299..0668676096 100644 --- a/lib/kernel/doc/src/logger.xml +++ b/lib/kernel/doc/src/logger.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger.xml</file> </header> - <module>logger</module> + <module since="OTP 21.0">logger</module> <modulesummary>API module for Logger, the standard logging facility in Erlang/OTP.</modulesummary> @@ -245,6 +245,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </desc> </datatype> <datatype> + <name name="olp_config"/> + <desc> + <p></p> + </desc> + </datatype> + <datatype> <name name="primary_config"/> <desc> <p>Primary configuration data for Logger. The following @@ -290,7 +296,10 @@ logger:error("error happened because: ~p", [Reason]). % Without macro <section> <title>Macros</title> - <p>The following macros are defined:</p> + <p>The following macros are defined in <c>logger.hrl</c>, which + is included in a module with the directive</p> + <code> + -include_lib("kernel/include/logger.hrl").</code> <list> <item><c>?LOG_EMERGENCY(StringOrReport[,Metadata])</c></item> @@ -331,9 +340,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </section> <funcs> <func> - <name>emergency(StringOrReport[,Metadata])</name> - <name>emergency(Format,Args[,Metadata])</name> - <name>emergency(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">emergency(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">emergency(Format,Args[,Metadata])</name> + <name since="OTP 21.0">emergency(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>emergency</c>.</fsummary> <desc> <p>Equivalent to @@ -342,9 +351,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>alert(StringOrReport[,Metadata])</name> - <name>alert(Format,Args[,Metadata])</name> - <name>alert(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">alert(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">alert(Format,Args[,Metadata])</name> + <name since="OTP 21.0">alert(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>alert</c>.</fsummary> <desc> <p>Equivalent to @@ -353,9 +362,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>critical(StringOrReport[,Metadata])</name> - <name>critical(Format,Args[,Metadata])</name> - <name>critical(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">critical(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">critical(Format,Args[,Metadata])</name> + <name since="OTP 21.0">critical(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>critical</c>.</fsummary> <desc> <p>Equivalent to @@ -364,9 +373,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>error(StringOrReport[,Metadata])</name> - <name>error(Format,Args[,Metadata])</name> - <name>error(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">error(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">error(Format,Args[,Metadata])</name> + <name since="OTP 21.0">error(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>error</c>.</fsummary> <desc> <p>Equivalent to @@ -375,9 +384,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>warning(StringOrReport[,Metadata])</name> - <name>warning(Format,Args[,Metadata])</name> - <name>warning(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">warning(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">warning(Format,Args[,Metadata])</name> + <name since="OTP 21.0">warning(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>warning</c>.</fsummary> <desc> <p>Equivalent to @@ -386,9 +395,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>notice(StringOrReport[,Metadata])</name> - <name>notice(Format,Args[,Metadata])</name> - <name>notice(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">notice(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">notice(Format,Args[,Metadata])</name> + <name since="OTP 21.0">notice(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>notice</c>.</fsummary> <desc> <p>Equivalent to @@ -397,9 +406,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>info(StringOrReport[,Metadata])</name> - <name>info(Format,Args[,Metadata])</name> - <name>info(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">info(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">info(Format,Args[,Metadata])</name> + <name since="OTP 21.0">info(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>info</c>.</fsummary> <desc> <p>Equivalent to @@ -408,9 +417,9 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name>debug(StringOrReport[,Metadata])</name> - <name>debug(Format,Args[,Metadata])</name> - <name>debug(Fun,FunArgs[,Metadata])</name> + <name since="OTP 21.0">debug(StringOrReport[,Metadata])</name> + <name since="OTP 21.0">debug(Format,Args[,Metadata])</name> + <name since="OTP 21.0">debug(Fun,FunArgs[,Metadata])</name> <fsummary>Logs the given message as level <c>debug</c>.</fsummary> <desc> <p>Equivalent to @@ -419,12 +428,12 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="log" arity="2"/> - <name name="log" arity="3" clause_i="1"/> - <name name="log" arity="3" clause_i="2"/> - <name name="log" arity="3" clause_i="3"/> - <name name="log" arity="4" clause_i="1"/> - <name name="log" arity="4" clause_i="2"/> + <name name="log" arity="2" since="OTP 21.0"/> + <name name="log" arity="3" clause_i="1" since="OTP 21.0"/> + <name name="log" arity="3" clause_i="2" since="OTP 21.0"/> + <name name="log" arity="3" clause_i="3" since="OTP 21.0"/> + <name name="log" arity="4" clause_i="1" since="OTP 21.0"/> + <name name="log" arity="4" clause_i="2" since="OTP 21.0"/> <fsummary>Logs the given message.</fsummary> <type variable="Level"/> <type variable="StringOrReport" name_i="1"/> @@ -445,7 +454,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </section> <funcs> <func> - <name name="add_handler" arity="3"/> + <name name="add_handler" arity="3" since="OTP 21.0"/> <fsummary>Add a handler with the given configuration.</fsummary> <desc> <p>Add a handler with the given configuration.</p> @@ -456,7 +465,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="add_handler_filter" arity="3"/> + <name name="add_handler_filter" arity="3" since="OTP 21.0"/> <fsummary>Add a filter to the specified handler.</fsummary> <desc> <p>Add a filter to the specified handler.</p> @@ -497,7 +506,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="add_handlers" arity="1" clause_i="1"/> + <name name="add_handlers" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Set up log handlers from the application's configuration parameters.</fsummary> <desc> @@ -507,7 +516,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro </func> <func> - <name name="add_handlers" arity="1" clause_i="2"/> + <name name="add_handlers" arity="1" clause_i="2" since="OTP 21.0"/> <fsummary>Setup logger handlers.</fsummary> <type name="config_handler"/> <desc> @@ -550,7 +559,7 @@ start(_, []) -> </func> <func> - <name name="add_primary_filter" arity="2"/> + <name name="add_primary_filter" arity="2" since="OTP 21.0"/> <fsummary>Add a primary filter to Logger.</fsummary> <desc> <p>Add a primary filter to Logger.</p> @@ -591,16 +600,16 @@ start(_, []) -> </func> <func> - <name name="get_config" arity="0"/> + <name name="get_config" arity="0" since="OTP 21.0"/> <fsummary>Look up the current Logger configuration</fsummary> <desc> - <p>Look up all current Logger configuration, including primary - and handler configuration, and module level settings.</p> + <p>Look up all current Logger configuration, including primary, + handler, and proxy configuration, and module level settings.</p> </desc> </func> <func> - <name name="get_handler_config" arity="0"/> + <name name="get_handler_config" arity="0" since="OTP 21.0"/> <fsummary>Look up the current configuration for all handlers.</fsummary> <desc> <p>Look up the current configuration for all handlers.</p> @@ -608,7 +617,7 @@ start(_, []) -> </func> <func> - <name name="get_handler_config" arity="1"/> + <name name="get_handler_config" arity="1" since="OTP 21.0"/> <fsummary>Look up the current configuration for the given handler.</fsummary> <desc> @@ -617,7 +626,7 @@ start(_, []) -> </func> <func> - <name name="get_handler_ids" arity="0"/> + <name name="get_handler_ids" arity="0" since="OTP 21.0"/> <fsummary>Look up the identities for all installed handlers.</fsummary> <desc> <p>Look up the identities for all installed handlers.</p> @@ -625,7 +634,7 @@ start(_, []) -> </func> <func> - <name name="get_primary_config" arity="0"/> + <name name="get_primary_config" arity="0" since="OTP 21.0"/> <fsummary>Look up the current primary configuration for Logger.</fsummary> <desc> <p>Look up the current primary configuration for Logger.</p> @@ -633,7 +642,18 @@ start(_, []) -> </func> <func> - <name name="get_module_level" arity="0"/> + <name name="get_proxy_config" arity="0" since="OTP 21.3"/> + <fsummary>Look up the current configuration for the Logger proxy.</fsummary> + <desc> + <p>Look up the current configuration for the Logger proxy.</p> + <p>For more information about the proxy, see + section <seealso marker="logger_chapter#proxy">Logger + Proxy</seealso> in the Kernel User's Guide.</p> + </desc> + </func> + + <func> + <name name="get_module_level" arity="0" since="OTP 21.0"/> <fsummary>Look up all current module levels.</fsummary> <desc> <p>Look up all current module levels. Returns a list @@ -645,7 +665,7 @@ start(_, []) -> </func> <func> - <name name="get_module_level" arity="1"/> + <name name="get_module_level" arity="1" since="OTP 21.0"/> <fsummary>Look up the current level for the given modules.</fsummary> <desc> <p>Look up the current level for the given modules. Returns a @@ -657,7 +677,7 @@ start(_, []) -> </func> <func> - <name name="get_process_metadata" arity="0"/> + <name name="get_process_metadata" arity="0" since="OTP 21.0"/> <fsummary>Retrieve data set with set_process_metadata/1.</fsummary> <desc> <p>Retrieve data set @@ -669,7 +689,7 @@ start(_, []) -> </func> <func> - <name name="remove_handler" arity="1"/> + <name name="remove_handler" arity="1" since="OTP 21.0"/> <fsummary>Remove the handler with the specified identity.</fsummary> <desc> <p>Remove the handler identified by <c><anno>HandlerId</anno></c>.</p> @@ -677,7 +697,7 @@ start(_, []) -> </func> <func> - <name name="remove_handler_filter" arity="2"/> + <name name="remove_handler_filter" arity="2" since="OTP 21.0"/> <fsummary>Remove a filter from the specified handler.</fsummary> <desc> <p>Remove the filter identified @@ -687,7 +707,7 @@ start(_, []) -> </func> <func> - <name name="remove_primary_filter" arity="1"/> + <name name="remove_primary_filter" arity="1" since="OTP 21.0"/> <fsummary>Remove a primary filter from Logger.</fsummary> <desc> <p>Remove the primary filter identified @@ -696,7 +716,7 @@ start(_, []) -> </func> <func> - <name name="set_application_level" arity="2"/> + <name name="set_application_level" arity="2" since="OTP 21.1"/> <fsummary>Set the log level for all modules in the specified application.</fsummary> <desc> <p>Set the log level for all the modules of the specified application.</p> @@ -707,7 +727,7 @@ start(_, []) -> </func> <func> - <name name="set_handler_config" arity="2"/> + <name name="set_handler_config" arity="2" since="OTP 21.0"/> <fsummary>Set configuration data for the specified handler.</fsummary> <desc> <p>Set configuration data for the specified handler. This @@ -728,11 +748,11 @@ start(_, []) -> </func> <func> - <name name="set_handler_config" arity="3" clause_i="1"/> - <name name="set_handler_config" arity="3" clause_i="2"/> - <name name="set_handler_config" arity="3" clause_i="3"/> - <name name="set_handler_config" arity="3" clause_i="4"/> - <name name="set_handler_config" arity="3" clause_i="5"/> + <name name="set_handler_config" arity="3" clause_i="1" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="2" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="3" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="4" since="OTP 21.0"/> + <name name="set_handler_config" arity="3" clause_i="5" since="OTP 21.0"/> <fsummary>Add or update configuration data for the specified handler.</fsummary> <type variable="HandlerId"/> @@ -764,7 +784,7 @@ start(_, []) -> </func> <func> - <name name="set_primary_config" arity="1"/> + <name name="set_primary_config" arity="1" since="OTP 21.0"/> <fsummary>Set primary configuration data for Logger.</fsummary> <desc> <p>Set primary configuration data for Logger. This @@ -782,9 +802,9 @@ start(_, []) -> </func> <func> - <name name="set_primary_config" arity="2" clause_i="1"/> - <name name="set_primary_config" arity="2" clause_i="2"/> - <name name="set_primary_config" arity="2" clause_i="3"/> + <name name="set_primary_config" arity="2" clause_i="1" since="OTP 21.0"/> + <name name="set_primary_config" arity="2" clause_i="2" since="OTP 21.0"/> + <name name="set_primary_config" arity="2" clause_i="3" since="OTP 21.0"/> <fsummary>Add or update primary configuration data for Logger.</fsummary> <type variable="Level" name_i="1"/> <type variable="FilterDefault" name_i="2"/> @@ -798,7 +818,28 @@ start(_, []) -> </func> <func> - <name name="set_module_level" arity="2"/> + <name name="set_proxy_config" arity="1" since="OTP 21.3"/> + <fsummary>Set configuration data for the Logger proxy.</fsummary> + <desc> + <p>Set configuration data for the Logger proxy. This + overwrites the current proxy configuration. Keys that are not + specified in the <c><anno>Config</anno></c> map gets default + values.</p> + <p>To modify the existing configuration, + use <seealso marker="#update_proxy_config-1"> + <c>update_proxy_config/1</c></seealso>, or, if a more + complex merge is needed, read the current configuration + with <seealso marker="#get_proxy_config-0"><c>get_proxy_config/0</c> + </seealso>, then do the merge before writing the new + configuration back with this function.</p> + <p>For more information about the proxy, see + section <seealso marker="logger_chapter#proxy">Logger + Proxy</seealso> in the Kernel User's Guide.</p> + </desc> + </func> + + <func> + <name name="set_module_level" arity="2" since="OTP 21.0"/> <fsummary>Set the log level for the specified modules.</fsummary> <desc> <p>Set the log level for the specified modules.</p> @@ -838,7 +879,7 @@ start(_, []) -> </func> <func> - <name name="set_process_metadata" arity="1"/> + <name name="set_process_metadata" arity="1" since="OTP 21.0"/> <fsummary>Set metadata to use when logging from current process.</fsummary> <desc> <p>Set metadata which Logger shall automatically insert in @@ -857,7 +898,7 @@ start(_, []) -> </func> <func> - <name name="unset_application_level" arity="1"/> + <name name="unset_application_level" arity="1" since="OTP 21.1"/> <fsummary>Unset the log level for all modules in the specified application.</fsummary> <desc> <p>Unset the log level for all the modules of the specified application.</p> @@ -868,7 +909,7 @@ start(_, []) -> </func> <func> - <name name="unset_module_level" arity="0"/> + <name name="unset_module_level" arity="0" since="OTP 21.0"/> <fsummary>Remove module specific log settings for all modules.</fsummary> <desc> <p>Remove module specific log settings. After this, the @@ -877,7 +918,7 @@ start(_, []) -> </func> <func> - <name name="unset_module_level" arity="1"/> + <name name="unset_module_level" arity="1" since="OTP 21.0"/> <fsummary>Remove module specific log settings for the given modules.</fsummary> <desc> @@ -887,7 +928,7 @@ start(_, []) -> </func> <func> - <name name="unset_process_metadata" arity="0"/> + <name name="unset_process_metadata" arity="0" since="OTP 21.0"/> <fsummary>Delete data set with set_process_metadata/1.</fsummary> <desc> <p>Delete data set @@ -899,7 +940,7 @@ start(_, []) -> </func> <func> - <name name="update_formatter_config" arity="2"/> + <name name="update_formatter_config" arity="2" since="OTP 21.0"/> <fsummary>Update the formatter configuration for the specified handler.</fsummary> <desc> <p>Update the formatter configuration for the specified handler.</p> @@ -914,7 +955,7 @@ start(_, []) -> </func> <func> - <name name="update_formatter_config" arity="3"/> + <name name="update_formatter_config" arity="3" since="OTP 21.0"/> <fsummary>Update the formatter configuration for the specified handler.</fsummary> <desc> <p>Update the formatter configuration for the specified handler.</p> @@ -925,7 +966,7 @@ start(_, []) -> </func> <func> - <name name="update_handler_config" arity="2"/> + <name name="update_handler_config" arity="2" since="OTP 21.0"/> <fsummary>Update configuration data for the specified handler.</fsummary> <desc> <p>Update configuration data for the specified handler. This function @@ -941,11 +982,11 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)). </func> <func> - <name name="update_handler_config" arity="3" clause_i="1"/> - <name name="update_handler_config" arity="3" clause_i="2"/> - <name name="update_handler_config" arity="3" clause_i="3"/> - <name name="update_handler_config" arity="3" clause_i="4"/> - <name name="update_handler_config" arity="3" clause_i="5"/> + <name name="update_handler_config" arity="3" clause_i="1" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="2" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="3" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="4" since="OTP 21.2"/> + <name name="update_handler_config" arity="3" clause_i="5" since="OTP 21.2"/> <fsummary>Add or update configuration data for the specified handler.</fsummary> <type variable="HandlerId"/> @@ -977,7 +1018,7 @@ logger:set_handler_config(HandlerId, maps:merge(Old, Config)). </func> <func> - <name name="update_primary_config" arity="1"/> + <name name="update_primary_config" arity="1" since="OTP 21.0"/> <fsummary>Update primary configuration data for Logger.</fsummary> <desc> <p>Update primary configuration data for Logger. This function @@ -993,7 +1034,7 @@ logger:set_primary_config(maps:merge(Old, Config)). </func> <func> - <name name="update_process_metadata" arity="1"/> + <name name="update_process_metadata" arity="1" since="OTP 21.0"/> <fsummary>Set or update metadata to use when logging from current process.</fsummary> <desc> @@ -1010,6 +1051,25 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </seealso>.</p> </desc> </func> + + <func> + <name name="update_proxy_config" arity="1" since="OTP 21.3"/> + <fsummary>Update configuration data for the Logger proxy.</fsummary> + <desc> + <p>Update configuration data for the Logger proxy. This function + behaves as if it was implemented as follows:</p> + <code type="erl"> +Old = logger:get_proxy_config(), +logger:set_proxy_config(maps:merge(Old, Config)). + </code> + <p>To overwrite the existing configuration without any merge, + use <seealso marker="#set_proxy_config-1"><c>set_proxy_config/1</c> + </seealso>.</p> + <p>For more information about the proxy, see + section <seealso marker="logger_chapter#proxy">Logger + Proxy</seealso> in the Kernel User's Guide.</p> + </desc> + </func> </funcs> <section> @@ -1018,7 +1078,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </section> <funcs> <func> - <name name="compare_levels" arity="2"/> + <name name="compare_levels" arity="2" since="OTP 21.0"/> <fsummary>Compare the severity of two log levels.</fsummary> <desc> <p>Compare the severity of two log levels. Returns <c>gt</c> @@ -1029,7 +1089,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name name="format_report" arity="1"/> + <name name="format_report" arity="1" since="OTP 21.0"/> <fsummary>Convert a log message on report form to {Format, Args}.</fsummary> <desc> <p>Convert a log message on report form to <c>{Format, @@ -1059,7 +1119,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <funcs> <func> - <name>HModule:adding_handler(Config1) -> {ok, Config2} | {error, + <name since="OTP 21.0">HModule:adding_handler(Config1) -> {ok, Config2} | {error, Reason}</name> <fsummary>An instance of this handler is about to be added.</fsummary> <type> @@ -1085,7 +1145,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name>HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name> + <name since="OTP 21.2">HModule:changing_config(SetOrUpdate, OldConfig, NewConfig) -> {ok, Config} | {error, Reason}</name> <fsummary>The configuration for this handler is about to change.</fsummary> <type> <v>SetOrUpdate = set | update</v> @@ -1123,7 +1183,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name>HModule:filter_config(Config) -> FilteredConfig</name> + <name since="OTP 21.2">HModule:filter_config(Config) -> FilteredConfig</name> <fsummary>Remove internal data from configuration.</fsummary> <type> <v>Config = FilteredConfig = @@ -1143,7 +1203,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name>HModule:log(LogEvent, Config) -> void()</name> + <name since="OTP 21.0">HModule:log(LogEvent, Config) -> void()</name> <fsummary>Log the given log event.</fsummary> <type> <v>LogEvent = @@ -1166,7 +1226,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </func> <func> - <name>HModule:removing_handler(Config) -> ok</name> + <name since="OTP 21.0">HModule:removing_handler(Config) -> ok</name> <fsummary>The given handler is about to be removed.</fsummary> <type> <v>Config = @@ -1194,7 +1254,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <funcs> <func> - <name>FModule:check_config(FConfig) -> ok | {error, Reason}</name> + <name since="OTP 21.0">FModule:check_config(FConfig) -> ok | {error, Reason}</name> <fsummary>Validate the given formatter configuration.</fsummary> <type> <v>FConfig = @@ -1214,7 +1274,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). <item><seealso marker="logger#set_handler_config-2"> <c>logger:set_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_handler_config-2"> - <c>logger:updata_handler_config/2,3</c></seealso></item> + <c>logger:update_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_formatter_config-2"> <c>logger:update_formatter_config/2</c></seealso></item> </list> @@ -1225,7 +1285,7 @@ logger:set_process_metadata(maps:merge(logger:get_process_metadata(), Meta)). </desc> </func> <func> - <name>FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name> + <name since="OTP 21.0">FModule:format(LogEvent, FConfig) -> FormattedLogEntry</name> <fsummary>Format the given log event.</fsummary> <type> <v>LogEvent = diff --git a/lib/kernel/doc/src/logger_chapter.xml b/lib/kernel/doc/src/logger_chapter.xml index 8fc52ce0a9..03b9edcf8f 100644 --- a/lib/kernel/doc/src/logger_chapter.xml +++ b/lib/kernel/doc/src/logger_chapter.xml @@ -113,7 +113,10 @@ of functions on the form <c>logger:Level/1,2,3</c>, which are all shortcuts for <seealso marker="logger#log-2"> - <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p> + <c>logger:log(Level,Arg1[,Arg2[,Arg3]])</c></seealso>.</p> + <p>The macros are defined in <c>logger.hrl</c>, which is included + in a module with the directive</p> + <code>-include_lib("kernel/include/logger.hrl").</code> <p>The difference between using the macros and the exported functions is that macros add location (originator) information to the metadata, and performs lazy evaluation by wrapping the @@ -690,8 +693,10 @@ logger:debug(#{got => connection_request, id => Id, state => State}, with <seealso marker="#logger_sasl_compatible"> <c>logger_sasl_compatible</c></seealso>.</p> <p>With this parameter, you can modify or disable the default - handler, add custom handlers and primary logger filters, and - set log levels per module.</p> + handler, add custom handlers and primary logger filters, set + log levels per module, and modify + the <seealso marker="#proxy">proxy</seealso> + configuration.</p> <p><c>Config</c> is any (zero or more) of the following:</p> <taglist> <tag><c>{handler, default, undefined}</c></tag> @@ -743,6 +748,14 @@ logger:debug(#{got => connection_request, id => Id, state => State}, <p>for each <c>Module</c>.</p> <p>Multiple entries of this type are allowed.</p> </item> + <tag><c>{proxy, ProxyConfig}</c></tag> + <item> + <p>Sets the proxy configuration, equivalent to calling</p> + <pre><seealso marker="logger#set_proxy_config/1"> + logger:set_proxy_config(ProxyConfig) + </seealso></pre> + <p>Only one entry of this type is allowed.</p> + </item> </taglist> <p>See section <seealso marker="#config_examples">Configuration @@ -1331,9 +1344,50 @@ logger:add_handler(my_disk_log_h, logger_disk_log_h, </section> <section> + <marker id="proxy"/> + <title>Logger Proxy</title> + <p>The Logger proxy is an Erlang process which is part of the + Kernel application's supervision tree. During startup, the proxy + process registers itself as the <c>system_logger</c>, meaning + that log events produced by the emulator are sent to this + process.</p> + <p>When a log event is issued on a process which has its group + leader on a remote node, Logger automatically forwards the log + event to the group leader's node. To achieve this, it first + sends the log event as an Erlang message from the original + client process to the proxy on the local node, and the proxy in + turn forwards the event to the proxy on the remote node.</p> + <p>When receiving a log event, either from the emulator or from a + remote node, the proxy calls the Logger API to log the event.</p> + <p>The proxy process is overload protected in the same way as + described in + section <seealso marker="#overload_protection">Protecting the + Handler from Overload</seealso>, but with the following default + values:</p> + <code> + #{sync_mode_qlen => 500, + drop_mode_qlen => 1000, + flush_qlen => 5000, + burst_limit_enable => false, + overload_kill_enable => false}</code> + <p>For log events from the emulator, synchronous message passing + mode is not applicable, since all messages are passed + asynchronously by the emulator. Drop mode is achieved by setting + the <c>system_logger</c> to <c>undefined</c>, forcing the + emulator to drop events until it is set back to the proxy pid + again.</p> + <p>The proxy uses <seealso marker="erts:erlang#send_nosuspend/2"> + <c>erlang:send_nosuspend/2</c></seealso> when sending log + events to a remote node. If the message could not be sent + without suspending the sender, it is dropped. This is to avoid + blocking the proxy process.</p> + </section> + + <section> <title>See Also</title> <p> <seealso marker="disk_log"><c>disk_log(3)</c></seealso>, + <seealso marker="erts:erlang"><c>erlang(3)</c></seealso>, <seealso marker="error_logger"><c>error_logger(3)</c></seealso>, <seealso marker="logger"><c>logger(3)</c></seealso>, <seealso marker="logger_disk_log_h"><c>logger_disk_log_h(3)</c></seealso>, diff --git a/lib/kernel/doc/src/logger_disk_log_h.xml b/lib/kernel/doc/src/logger_disk_log_h.xml index d9b941a0a9..5b2374690e 100644 --- a/lib/kernel/doc/src/logger_disk_log_h.xml +++ b/lib/kernel/doc/src/logger_disk_log_h.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_disk_log_h.xml</file> </header> - <module>logger_disk_log_h</module> + <module since="OTP 21.0">logger_disk_log_h</module> <modulesummary>A disk_log based handler for Logger</modulesummary> <description> @@ -148,7 +148,7 @@ erl -kernel logger '[{handler,default,logger_disk_log_h, <funcs> <func> - <name name="filesync" arity="1" clause_i="1"/> + <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Writes buffered data to disk.</fsummary> <desc> <p>Write buffered data to disk.</p> diff --git a/lib/kernel/doc/src/logger_filters.xml b/lib/kernel/doc/src/logger_filters.xml index 90f1fcc270..0a02342864 100644 --- a/lib/kernel/doc/src/logger_filters.xml +++ b/lib/kernel/doc/src/logger_filters.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_filters.xml</file> </header> - <module>logger_filters</module> + <module since="OTP 21.0">logger_filters</module> <modulesummary>Filters to use with Logger.</modulesummary> <description> @@ -51,7 +51,7 @@ <funcs> <func> - <name name="domain" arity="2"/> + <name name="domain" arity="2" since="OTP 21.0"/> <fsummary>Filter log events based on the domain field in metadata.</fsummary> <desc> @@ -152,7 +152,7 @@ ok</code> </func> <func> - <name name="level" arity="2"/> + <name name="level" arity="2" since="OTP 21.0"/> <fsummary>Filter log events based on the log level.</fsummary> <desc> <p>This filter provides a way of filtering log events based @@ -212,7 +212,7 @@ ok</code> </func> <func> - <name name="progress" arity="2"/> + <name name="progress" arity="2" since="OTP 21.0"/> <fsummary>Filter progress reports from supervisor and application_controller.</fsummary> <desc> <p>This filter matches all progress reports @@ -227,7 +227,7 @@ ok</code> </func> <func> - <name name="remote_gl" arity="2"/> + <name name="remote_gl" arity="2" since="OTP 21.0"/> <fsummary>Filter events with group leader on remote node.</fsummary> <desc> <p>This filter matches all events originating from a process diff --git a/lib/kernel/doc/src/logger_formatter.xml b/lib/kernel/doc/src/logger_formatter.xml index 24772fd6c4..6dc83d24e1 100644 --- a/lib/kernel/doc/src/logger_formatter.xml +++ b/lib/kernel/doc/src/logger_formatter.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_formatter.xml</file> </header> - <module>logger_formatter</module> + <module since="OTP 21.0">logger_formatter</module> <modulesummary>Default formatter for Logger.</modulesummary> <description> @@ -289,7 +289,7 @@ exit_reason: "It crashed"</code> <funcs> <func> - <name name="check_config" arity="1"/> + <name name="check_config" arity="1" since="OTP 21.0"/> <fsummary>Validates the given formatter configuration.</fsummary> <desc> <p>The function is called by Logger when the formatter @@ -303,14 +303,14 @@ exit_reason: "It crashed"</code> <item><seealso marker="logger#set_handler_config-2"> <c>logger:set_handler_config/2,3</c></seealso></item> <item><seealso marker="logger#update_handler_config-2"> - <c>logger:updata_handler_config/2</c></seealso></item> + <c>logger:update_handler_config/2</c></seealso></item> <item><seealso marker="logger#update_formatter_config-2"> <c>logger:update_formatter_config/2</c></seealso></item> </list> </desc> </func> <func> - <name name="format" arity="2"/> + <name name="format" arity="2" since="OTP 21.0"/> <fsummary>Formats the given message.</fsummary> <desc> <p>This the formatter callback function to be called from diff --git a/lib/kernel/doc/src/logger_std_h.xml b/lib/kernel/doc/src/logger_std_h.xml index e156f5719b..fcd180abd6 100644 --- a/lib/kernel/doc/src/logger_std_h.xml +++ b/lib/kernel/doc/src/logger_std_h.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>logger_std_h.xml</file> </header> - <module>logger_std_h</module> + <module since="OTP 21.0">logger_std_h</module> <modulesummary>Standard handler for Logger.</modulesummary> <description> @@ -121,7 +121,7 @@ erl -kernel logger '[{handler,default,logger_std_h, <funcs> <func> - <name name="filesync" arity="1" clause_i="1"/> + <name name="filesync" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Writes buffered data to disk.</fsummary> <desc> <p>Write buffered data to disk.</p> diff --git a/lib/kernel/doc/src/net_adm.xml b/lib/kernel/doc/src/net_adm.xml index 6957a3b5e4..c3e1619f1b 100644 --- a/lib/kernel/doc/src/net_adm.xml +++ b/lib/kernel/doc/src/net_adm.xml @@ -28,7 +28,7 @@ <date>1996-09-10</date> <rev>A</rev> </header> - <module>net_adm</module> + <module since="">net_adm</module> <modulesummary>Various Erlang net administration routines.</modulesummary> <description> <p>This module contains various network utility functions.</p> @@ -36,7 +36,7 @@ <funcs> <func> - <name name="dns_hostname" arity="1"/> + <name name="dns_hostname" arity="1" since=""/> <fsummary>Official name of a host.</fsummary> <desc> <p>Returns the official name of <c><anno>Host</anno></c>, or @@ -46,7 +46,7 @@ </func> <func> - <name name="host_file" arity="0"/> + <name name="host_file" arity="0" since=""/> <fsummary>Read file <c>.hosts.erlang</c>.</fsummary> <desc> <p>Reads file <c>.hosts.erlang</c>, see section @@ -58,7 +58,7 @@ </func> <func> - <name name="localhost" arity="0"/> + <name name="localhost" arity="0" since=""/> <fsummary>Name of the local host.</fsummary> <desc> <p>Returns the name of the local host. If Erlang was started @@ -68,8 +68,8 @@ </func> <func> - <name name="names" arity="0"/> - <name name="names" arity="1"/> + <name name="names" arity="0" since=""/> + <name name="names" arity="1" since=""/> <fsummary>Names of Erlang nodes at a host.</fsummary> <desc> <p>Similar to <c>epmd -names</c>, see @@ -86,7 +86,7 @@ </func> <func> - <name name="ping" arity="1"/> + <name name="ping" arity="1" since=""/> <fsummary>Set up a connection to a node.</fsummary> <desc> <p>Sets up a connection to <c><anno>Node</anno></c>. Returns @@ -95,8 +95,8 @@ </func> <func> - <name name="world" arity="0"/> - <name name="world" arity="1"/> + <name name="world" arity="0" since=""/> + <name name="world" arity="1" since=""/> <fsummary>Lookup and connect to all nodes at all hosts in <c>.hosts.erlang</c>.</fsummary> <type name="verbosity"/> @@ -117,8 +117,8 @@ </func> <func> - <name name="world_list" arity="1"/> - <name name="world_list" arity="2"/> + <name name="world_list" arity="1" since=""/> + <name name="world_list" arity="2" since=""/> <fsummary>Lookup and connect to all nodes at specified hosts.</fsummary> <type name="verbosity"/> <desc> diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml index bfbe7a6470..419d3cad84 100644 --- a/lib/kernel/doc/src/net_kernel.xml +++ b/lib/kernel/doc/src/net_kernel.xml @@ -28,7 +28,7 @@ <date>1996-09-10</date> <rev>A</rev> </header> - <module>net_kernel</module> + <module since="">net_kernel</module> <modulesummary>Erlang networking kernel.</modulesummary> <description> <p>The net kernel is a system process, registered as @@ -81,7 +81,7 @@ $ <input>erl -sname foobar</input></pre> <funcs> <func> - <name name="allow" arity="1"/> + <name name="allow" arity="1" since=""/> <fsummary>Permit access to a specified set of nodes</fsummary> <desc> <p>Permits access to the specified set of nodes.</p> @@ -98,7 +98,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="connect_node" arity="1"/> + <name name="connect_node" arity="1" since=""/> <fsummary>Establish a connection to a node.</fsummary> <desc> <p>Establishes a connection to <c><anno>Node</anno></c>. Returns @@ -110,7 +110,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="get_net_ticktime" arity="0"/> + <name name="get_net_ticktime" arity="0" since=""/> <fsummary>Get <c>net_ticktime</c>.</fsummary> <desc> <p>Gets <c>net_ticktime</c> (see @@ -131,7 +131,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="getopts" arity="2"/> + <name name="getopts" arity="2" since="OTP 19.1"/> <fsummary>Get distribution socket options.</fsummary> <desc> <p>Get one or more options for the distribution socket @@ -146,8 +146,8 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="monitor_nodes" arity="1"/> - <name name="monitor_nodes" arity="2"/> + <name name="monitor_nodes" arity="1" since=""/> + <name name="monitor_nodes" arity="2" since=""/> <fsummary>Subscribe to node status change messages.</fsummary> <desc> <p>The calling process subscribes or unsubscribes to node @@ -267,8 +267,8 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="set_net_ticktime" arity="1"/> - <name name="set_net_ticktime" arity="2"/> + <name name="set_net_ticktime" arity="1" since=""/> + <name name="set_net_ticktime" arity="2" since=""/> <fsummary>Set <c>net_ticktime</c>.</fsummary> <desc> <p>Sets <c>net_ticktime</c> (see @@ -324,7 +324,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="setopts" arity="2"/> + <name name="setopts" arity="2" since="OTP 19.1"/> <fsummary>Set distribution socket options.</fsummary> <desc> <p>Set one or more options for distribution sockets. @@ -345,9 +345,9 @@ $ <input>erl -sname foobar</input></pre> </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> + <name since="">start([Name]) -> {ok, pid()} | {error, Reason}</name> + <name since="">start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name> + <name since="">start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name> <fsummary>Turn an Erlang runtime system into a distributed node.</fsummary> <type> <v>Name = atom()</v> @@ -364,7 +364,7 @@ $ <input>erl -sname foobar</input></pre> </func> <func> - <name name="stop" arity="0"/> + <name name="stop" arity="0" since=""/> <fsummary>Turn a node into a non-distributed Erlang runtime system.</fsummary> <desc> <p>Turns a distributed node into a non-distributed node. For diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index 8188ede6a2..021ecfa40d 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,123 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 6.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A new function, <c>logger:update_handler_config/3</c> is + added, and the handler callback <c>changing_config</c> + now has a new argument, <c>SetOrUpdate</c>, which + indicates if the configuration change comes from + <c>set_handler_config/2,3</c> or + <c>update_handler_config/2,3</c>.</p> + <p> + This allows the handler to consistently merge the new + configuration with the old (if the change comes from + <c>update_handler_config/2,3</c>) or with the default (if + the change comes from <c>set_handler_config/2,3</c>).</p> + <p> + The built-in handlers <c>logger_std_h</c> and + <c>logger_disk_log_h</c> are updated accordingly. A bug + which could cause inconsistency between the handlers' + internal state and the stored configuration is also + corrected.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-15364</p> + </item> + <item> + <p> + Fix fallback when custom erl_epmd client does not + implement address_please.</p> + <p> + Own Id: OTP-15388 Aux Id: PR-1983 </p> + </item> + <item> + <p> + The logger ets table did not have the + <c>read_concurrency</c> option. This is now added.</p> + <p> + Own Id: OTP-15453 Aux Id: ERL-782 </p> + </item> + <item> + <p> + During system start, logger has a simple handler which + prints to stdout. After the kernel supervision is + started, this handler is removed and replaced by the + default handler. Due to a bug, logger earlier issued a + debug printout saying it received an unexpected message, + which was the EXIT message from the simple handler's + process. This is now corrected. The simple handler's + process now unlinks from the logger process before + terminating.</p> + <p> + Own Id: OTP-15466 Aux Id: ERL-788 </p> + </item> + <item> + <p> + The logger handler <c>logger_std_h</c> would not + re-create it's log file if it was removed. Due to this it + could not be used with tools like 'logrotate'. This is + now corrected.</p> + <p> + Own Id: OTP-15469</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + A function <c>inet:getifaddrs/1</c> that takes a list + with a namespace option has been added, for platforms + that support that feature, for example Linux (only?).</p> + <p> + Own Id: OTP-15121 Aux Id: ERIERL-189, PR-1974 </p> + </item> + <item> + <p>Added the <c>nopush</c> option for TCP sockets, which + corresponds to <c>TCP_NOPUSH</c> on *BSD and + <c>TCP_CORK</c> on Linux.</p> + <p>This is also used internally in <c>file:sendfile</c> + to reduce latency on subsequent send operations.</p> + <p> + Own Id: OTP-15357 Aux Id: ERL-698 </p> + </item> + <item> + <p> + Optimize handling of send_delay for tcp sockes to better + work with the new pollthread implementation introduced in + OTP-21.</p> + <p> + Own Id: OTP-15471 Aux Id: ERIERL-229 </p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 6.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix bug causing net_kernel process crash on connection + attempt from node with name identical to local node.</p> + <p> + Own Id: OTP-15438 Aux Id: ERL-781 </p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 6.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/doc/src/os.xml b/lib/kernel/doc/src/os.xml index c95e615c6b..0500e4cfb3 100644 --- a/lib/kernel/doc/src/os.xml +++ b/lib/kernel/doc/src/os.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>os</module> + <module since="">os</module> <modulesummary>Operating system-specific functions.</modulesummary> <description> <p>The functions in this module are operating system-specific. @@ -134,8 +134,8 @@ <funcs> <func> - <name name="cmd" arity="1"/> - <name name="cmd" arity="2"/> + <name name="cmd" arity="1" since=""/> + <name name="cmd" arity="2" since="OTP 20.2.3"/> <fsummary>Execute a command in a shell of the target OS.</fsummary> <desc> <p>Executes <c><anno>Command</anno></c> in a command shell of the @@ -173,8 +173,8 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="find_executable" arity="1"/> - <name name="find_executable" arity="2"/> + <name name="find_executable" arity="1" since=""/> + <name name="find_executable" arity="2" since=""/> <fsummary>Absolute filename of a program.</fsummary> <desc> <p>These two functions look up an executable program, with the @@ -190,7 +190,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getenv" arity="0"/> + <name name="getenv" arity="0" since=""/> <fsummary>List all environment variables.</fsummary> <desc> <p>Returns a list of all environment variables. @@ -205,7 +205,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getenv" arity="1"/> + <name name="getenv" arity="1" since=""/> <fsummary>Get the value of an environment variable.</fsummary> <desc> <p>Returns the <c><anno>Value</anno></c> of the environment variable @@ -220,7 +220,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getenv" arity="2"/> + <name name="getenv" arity="2" since="OTP 18.0"/> <fsummary>Get the value of an environment variable.</fsummary> <desc> <p>Returns the <c><anno>Value</anno></c> of the environment variable @@ -235,7 +235,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="getpid" arity="0"/> + <name name="getpid" arity="0" since=""/> <fsummary>Return the process identifier of the emulator process.</fsummary> <desc> @@ -251,7 +251,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="putenv" arity="2"/> + <name name="putenv" arity="2" since=""/> <fsummary>Set a new value for an environment variable.</fsummary> <desc> <p>Sets a new <c><anno>Value</anno></c> for environment variable @@ -277,7 +277,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="set_signal" arity="2"/> + <name name="set_signal" arity="2" since="OTP 20.0"/> <fsummary>Enables or disables handling of OS signals.</fsummary> <desc> <p>Enables or disables OS signals.</p> @@ -304,7 +304,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="system_time" arity="0"/> + <name name="system_time" arity="0" since="OTP 18.0"/> <fsummary>Current OS system time.</fsummary> <desc> <p>Returns the current @@ -317,7 +317,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="system_time" arity="1"/> + <name name="system_time" arity="1" since="OTP 18.0"/> <fsummary>Current OS system time.</fsummary> <desc> <p>Returns the current @@ -332,7 +332,7 @@ DirOut = os:cmd("dir"), % on Win32 platform</code> </func> <func> - <name name="timestamp" arity="0"/> + <name name="timestamp" arity="0" since=""/> <fsummary>Current OS system time on the <c>erlang:timestamp/0</c> format.</fsummary> <type_desc variable="Timestamp">Timestamp = {MegaSecs, Secs, MicroSecs}</type_desc> <desc> @@ -373,7 +373,7 @@ calendar:now_to_universal_time(TS), </func> <func> - <name name="perf_counter" arity="0"/> + <name name="perf_counter" arity="0" since="OTP 19.0"/> <fsummary>Returns a performance counter</fsummary> <desc> <p>Returns the current performance counter value in <c>perf_counter</c> @@ -383,7 +383,7 @@ calendar:now_to_universal_time(TS), </desc> </func> <func> - <name name="perf_counter" arity="1"/> + <name name="perf_counter" arity="1" since="OTP 19.0"/> <fsummary>Returns a performance counter</fsummary> <desc><p>Returns a performance counter that can be used as a very fast and high resolution timestamp. This counter is read directly from the hardware or operating @@ -397,7 +397,7 @@ calendar:now_to_universal_time(TS), </desc> </func> <func> - <name name="type" arity="0"/> + <name name="type" arity="0" since=""/> <fsummary>Return the OS family and, in some cases, the OS name of the current OS.</fsummary> <desc> @@ -417,7 +417,7 @@ calendar:now_to_universal_time(TS), </func> <func> - <name name="unsetenv" arity="1"/> + <name name="unsetenv" arity="1" since="OTP R16B03"/> <fsummary>Delete an environment variable.</fsummary> <desc> <p>Deletes the environment variable <c><anno>VarName</anno></c>.</p> @@ -429,7 +429,7 @@ calendar:now_to_universal_time(TS), </func> <func> - <name name="version" arity="0"/> + <name name="version" arity="0" since=""/> <fsummary>Return the OS versions.</fsummary> <desc> <p>Returns the OS version. diff --git a/lib/kernel/doc/src/pg2.xml b/lib/kernel/doc/src/pg2.xml index 0631b317b4..058d711756 100644 --- a/lib/kernel/doc/src/pg2.xml +++ b/lib/kernel/doc/src/pg2.xml @@ -32,7 +32,7 @@ <rev>A2</rev> <file>pg2.xml</file> </header> - <module>pg2</module> + <module since="">pg2</module> <modulesummary>Distributed named process groups.</modulesummary> <description> <p>This module implements process groups. Each message can be sent @@ -66,7 +66,7 @@ <funcs> <func> - <name name="create" arity="1"/> + <name name="create" arity="1" since=""/> <fsummary>Create a new, empty process group.</fsummary> <desc> <p>Creates a new, empty process group. The group is globally @@ -75,7 +75,7 @@ </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Delete a process group.</fsummary> <desc> <p>Deletes a process group.</p> @@ -83,7 +83,7 @@ </func> <func> - <name name="get_closest_pid" arity="1"/> + <name name="get_closest_pid" arity="1" since=""/> <fsummary>Common dispatch function.</fsummary> <desc> <p>A useful dispatch function that can be used from @@ -93,7 +93,7 @@ </func> <func> - <name name="get_local_members" arity="1"/> + <name name="get_local_members" arity="1" since=""/> <fsummary>Return all local processes in a group.</fsummary> <desc> <p>Returns all processes running on the local node in the @@ -104,7 +104,7 @@ </func> <func> - <name name="get_members" arity="1"/> + <name name="get_members" arity="1" since=""/> <fsummary>Return all processes in a group.</fsummary> <desc> <p>Returns all processes in the group <c>Name</c>. This @@ -114,7 +114,7 @@ </func> <func> - <name name="join" arity="2"/> + <name name="join" arity="2" since=""/> <fsummary>Join a process to a group.</fsummary> <desc> <p>Joins the process <c>Pid</c> to the group <c>Name</c>. @@ -124,7 +124,7 @@ </func> <func> - <name name="leave" arity="2"/> + <name name="leave" arity="2" since=""/> <fsummary>Make a process leave a group.</fsummary> <desc> <p>Makes the process <c>Pid</c> leave the group <c>Name</c>. @@ -134,8 +134,8 @@ </func> <func> - <name name="start" arity="0"/> - <name name="start_link" arity="0"/> + <name name="start" arity="0" since=""/> + <name name="start_link" arity="0" since=""/> <fsummary>Start the <c>pg2</c> server.</fsummary> <desc> <p>Starts the <c>pg2</c> server. Normally, the server does not need @@ -149,7 +149,7 @@ </func> <func> - <name name="which_groups" arity="0"/> + <name name="which_groups" arity="0" since=""/> <fsummary>Return a list of all known groups.</fsummary> <desc> <p>Returns a list of all known groups.</p> diff --git a/lib/kernel/doc/src/rpc.xml b/lib/kernel/doc/src/rpc.xml index fab616e630..c55454506e 100644 --- a/lib/kernel/doc/src/rpc.xml +++ b/lib/kernel/doc/src/rpc.xml @@ -28,7 +28,7 @@ <date>1996-09-10</date> <rev>A</rev> </header> - <module>rpc</module> + <module since="">rpc</module> <modulesummary>Remote Procedure Call services.</modulesummary> <description> <p>This module contains services similar to Remote @@ -51,7 +51,7 @@ <funcs> <func> - <name name="abcast" arity="2"/> + <name name="abcast" arity="2" since=""/> <fsummary>Broadcast a message asynchronously to a registered process on all nodes.</fsummary> <desc> @@ -61,7 +61,7 @@ </func> <func> - <name name="abcast" arity="3"/> + <name name="abcast" arity="3" since=""/> <fsummary>Broadcast a message asynchronously to a registered process on specific nodes.</fsummary> <desc> @@ -72,7 +72,7 @@ </func> <func> - <name name="async_call" arity="4"/> + <name name="async_call" arity="4" since=""/> <fsummary>Evaluate a function call on a node, asynchronous version.</fsummary> <desc> @@ -98,7 +98,7 @@ </func> <func> - <name name="block_call" arity="4"/> + <name name="block_call" arity="4" since=""/> <fsummary>Evaluate a function call on a node in the RPC server's context.</fsummary> <desc> @@ -115,7 +115,7 @@ </func> <func> - <name name="block_call" arity="5"/> + <name name="block_call" arity="5" since=""/> <fsummary>Evaluate a function call on a node in the RPC server's context.</fsummary> <desc> @@ -127,7 +127,7 @@ </func> <func> - <name name="call" arity="4"/> + <name name="call" arity="4" since=""/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>, @@ -138,7 +138,7 @@ </func> <func> - <name name="call" arity="5"/> + <name name="call" arity="5" since=""/> <fsummary>Evaluate a function call on a node.</fsummary> <desc> <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>, @@ -158,7 +158,7 @@ </func> <func> - <name name="cast" arity="4"/> + <name name="cast" arity="4" since=""/> <fsummary>Run a function on a node ignoring the result.</fsummary> <desc> <p>Evaluates <c>apply(<anno>Module</anno>, <anno>Function</anno>, @@ -171,7 +171,7 @@ </func> <func> - <name name="eval_everywhere" arity="3"/> + <name name="eval_everywhere" arity="3" since=""/> <fsummary>Run a function on all nodes, ignoring the result.</fsummary> <desc> <p>Equivalent to <c>eval_everywhere([node()|nodes()], @@ -181,7 +181,7 @@ </func> <func> - <name name="eval_everywhere" arity="4"/> + <name name="eval_everywhere" arity="4" since=""/> <fsummary>Run a function on specific nodes, ignoring the result.</fsummary> <desc> @@ -192,7 +192,7 @@ </func> <func> - <name name="multi_server_call" arity="2"/> + <name name="multi_server_call" arity="2" since=""/> <fsummary>Interact with the servers on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multi_server_call([node()|nodes()], @@ -201,7 +201,7 @@ </func> <func> - <name name="multi_server_call" arity="3"/> + <name name="multi_server_call" arity="3" since=""/> <fsummary>Interact with the servers on a number of nodes.</fsummary> <desc> <p>Can be used when interacting with servers called @@ -224,7 +224,7 @@ </func> <func> - <name name="multicall" arity="3"/> + <name name="multicall" arity="3" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>, @@ -233,7 +233,7 @@ </func> <func> - <name name="multicall" arity="4" clause_i="1"/> + <name name="multicall" arity="4" clause_i="1" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multicall(<anno>Nodes</anno>, <anno>Module</anno>, @@ -242,7 +242,7 @@ </func> <func> - <name name="multicall" arity="4" clause_i="2"/> + <name name="multicall" arity="4" clause_i="2" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>Equivalent to <c>multicall([node()|nodes()], <anno>Module</anno>, @@ -252,7 +252,7 @@ </func> <func> - <name name="multicall" arity="5"/> + <name name="multicall" arity="5" since=""/> <fsummary>Evaluate a function call on a number of nodes.</fsummary> <desc> <p>In contrast to an RPC, a multicall is an RPC that is sent @@ -288,7 +288,7 @@ </func> <func> - <name name="nb_yield" arity="1"/> + <name name="nb_yield" arity="1" since=""/> <fsummary>Deliver the result of evaluating a function call on a node (non-blocking).</fsummary> <desc> @@ -297,7 +297,7 @@ </func> <func> - <name name="nb_yield" arity="2"/> + <name name="nb_yield" arity="2" since=""/> <fsummary>Deliver the result of evaluating a function call on a node (non-blocking).</fsummary> <desc> @@ -315,7 +315,7 @@ </func> <func> - <name name="parallel_eval" arity="1"/> + <name name="parallel_eval" arity="1" since=""/> <fsummary>Evaluate many function calls on all nodes in parallel.</fsummary> <desc> @@ -328,7 +328,7 @@ </func> <func> - <name name="pinfo" arity="1"/> + <name name="pinfo" arity="1" since=""/> <fsummary>Information about a process.</fsummary> <desc> <p>Location transparent version of the BIF @@ -337,8 +337,8 @@ </func> <func> - <name name="pinfo" arity="2" clause_i="1"/> - <name name="pinfo" arity="2" clause_i="2"/> + <name name="pinfo" arity="2" clause_i="1" since=""/> + <name name="pinfo" arity="2" clause_i="2" since=""/> <fsummary>Information about a process.</fsummary> <desc> <p>Location transparent version of the BIF @@ -347,7 +347,7 @@ </func> <func> - <name name="pmap" arity="3"/> + <name name="pmap" arity="3" since=""/> <fsummary>Parallel evaluation of mapping a function over a list.</fsummary> <desc> @@ -360,7 +360,7 @@ </func> <func> - <name name="sbcast" arity="2"/> + <name name="sbcast" arity="2" since=""/> <fsummary>Broadcast a message synchronously to a registered process on all nodes.</fsummary> <desc> @@ -370,7 +370,7 @@ </func> <func> - <name name="sbcast" arity="3"/> + <name name="sbcast" arity="3" since=""/> <fsummary>Broadcast a message synchronously to a registered process on specific nodes.</fsummary> <desc> @@ -391,7 +391,7 @@ </func> <func> - <name name="server_call" arity="4"/> + <name name="server_call" arity="4" since=""/> <fsummary>Interact with a server on a node.</fsummary> <desc> <p>Can be used when interacting with a server called @@ -410,7 +410,7 @@ </func> <func> - <name name="yield" arity="1"/> + <name name="yield" arity="1" since=""/> <fsummary>Deliver the result of evaluating a function call on a node (blocking).</fsummary> <desc> diff --git a/lib/kernel/doc/src/seq_trace.xml b/lib/kernel/doc/src/seq_trace.xml index 1a4a74419a..aa29223dd0 100644 --- a/lib/kernel/doc/src/seq_trace.xml +++ b/lib/kernel/doc/src/seq_trace.xml @@ -28,7 +28,7 @@ <date>1998-04-16</date> <rev>A</rev> </header> - <module>seq_trace</module> + <module since="">seq_trace</module> <modulesummary>Sequential tracing of messages.</modulesummary> <description> <p>Sequential tracing makes it possible to trace all messages @@ -51,7 +51,7 @@ </datatypes> <funcs> <func> - <name name="set_token" arity="1"/> + <name name="set_token" arity="1" since=""/> <fsummary>Set the trace token</fsummary> <desc> <p>Sets the trace token for the calling process to <c><anno>Token</anno></c>. @@ -71,7 +71,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="set_token" arity="2"/> + <name name="set_token" arity="2" since=""/> <fsummary>Set a component of the trace token</fsummary> <type name="component"/> <type name="flag"/> @@ -158,7 +158,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="get_token" arity="0"/> + <name name="get_token" arity="0" since=""/> <fsummary>Return the value of the trace token</fsummary> <desc> <p>Returns the value of the trace token for the calling process. @@ -169,7 +169,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="get_token" arity="1"/> + <name name="get_token" arity="1" since=""/> <fsummary>Return the value of a trace token component</fsummary> <type name="component"/> <type name="flag"/> @@ -182,7 +182,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="print" arity="1"/> + <name name="print" arity="1" since=""/> <fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary> <desc> <p>Puts the Erlang term <c><anno>TraceInfo</anno></c> into the sequential @@ -192,7 +192,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="print" arity="2"/> + <name name="print" arity="2" since=""/> <fsummary>Put the Erlang term <c>TraceInfo</c>into the sequential trace output</fsummary> <desc> <p>Same as <c>print/1</c> with the additional condition that @@ -201,7 +201,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="reset_trace" arity="0"/> + <name name="reset_trace" arity="0" since=""/> <fsummary>Stop all sequential tracing on the local node</fsummary> <desc> <p>Sets the trace token to empty for all processes on the @@ -213,7 +213,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="set_system_tracer" arity="1"/> + <name name="set_system_tracer" arity="1" since=""/> <fsummary>Set the system tracer</fsummary> <type name="tracer"/> <desc> @@ -227,7 +227,7 @@ seq_trace:set_token(OldToken), % activate the trace token again </desc> </func> <func> - <name name="get_system_tracer" arity="0"/> + <name name="get_system_tracer" arity="0" since=""/> <fsummary>Return the pid() or port() of the current system tracer.</fsummary> <type name="tracer"/> <desc> diff --git a/lib/kernel/doc/src/wrap_log_reader.xml b/lib/kernel/doc/src/wrap_log_reader.xml index 7fb9c1c023..5f37e7ec5f 100644 --- a/lib/kernel/doc/src/wrap_log_reader.xml +++ b/lib/kernel/doc/src/wrap_log_reader.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>wrap_log_reader.sgml</file> </header> - <module>wrap_log_reader</module> + <module since="">wrap_log_reader</module> <modulesummary>A service to read internally formatted wrap disk logs. </modulesummary> <description> @@ -65,8 +65,8 @@ <funcs> <func> - <name name="chunk" arity="1"/> - <name name="chunk" arity="2"/> + <name name="chunk" arity="1" since=""/> + <name name="chunk" arity="2" since=""/> <fsummary>Read a chunk of objects written to a wrap log.</fsummary> <type name="chunk_ret"/> <desc> @@ -105,7 +105,7 @@ </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a log.</fsummary> <desc> <p>Closes a log file properly.</p> @@ -113,8 +113,8 @@ </func> <func> - <name name="open" arity="1"/> - <name name="open" arity="2"/> + <name name="open" arity="1" since=""/> + <name name="open" arity="2" since=""/> <fsummary>Open a log file.</fsummary> <type name="open_ret"/> <desc> diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index 57f17defc8..3d1506ea08 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -118,6 +118,8 @@ MODULES = \ logger_h_common \ logger_filters \ logger_formatter \ + logger_olp \ + logger_proxy \ logger_server \ logger_simple_h \ logger_sup \ @@ -151,7 +153,7 @@ INTERNAL_HRL_FILES= application_master.hrl disk_log.hrl \ inet_dns.hrl inet_res.hrl \ inet_boot.hrl inet_config.hrl inet_int.hrl \ inet_dns_record_adts.hrl \ - logger_internal.hrl logger_h_common.hrl + logger_internal.hrl logger_olp.hrl logger_h_common.hrl ERL_FILES= $(MODULES:%=%.erl) @@ -279,6 +281,8 @@ $(EBIN)/logger_config.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_disk_log_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl $(EBIN)/logger_filters.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_formatter.beam: logger_internal.hrl ../include/logger.hrl +$(EBIN)/logger_olp.beam: logger_olp.hrl logger_internal.hrl +$(EBIN)/logger_proxy.beam: logger_internal.hrl $(EBIN)/logger_server.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_simple_h.beam: logger_internal.hrl ../include/logger.hrl $(EBIN)/logger_std_h.beam: logger_h_common.hrl logger_internal.hrl ../include/logger.hrl ../include/file.hrl diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index fe073621c8..a1d9e8e215 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -68,6 +68,8 @@ logger_formatter, logger_h_common, logger_handler_watcher, + logger_olp, + logger_proxy, logger_server, logger_simple_h, logger_std_h, diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src index 0c0435e051..ccf0a82ced 100644 --- a/lib/kernel/src/kernel.appup.src +++ b/lib/kernel/src/kernel.appup.src @@ -16,15 +16,42 @@ %% limitations under the License. %% %% %CopyrightEnd% +%% +%% We allow upgrade from, and downgrade to all previous +%% versions from the following OTP releases: +%% - OTP 20 +%% - OTP 21 +%% +%% We also allow upgrade from, and downgrade to all +%% versions that have branched off from the above +%% stated previous versions. +%% {"%VSN%", - %% Up from - max one major revision back - [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 - {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 - {<<"6\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21.1 - %% Down to - max one major revision back - [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 - {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 - {<<"6\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.1 -}. + [{<<"^5\\.3$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.0$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.1$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + [{<<"^5\\.3$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.0$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^6\\.1$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^6\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl index 6762998d4f..abdd9a9ceb 100644 --- a/lib/kernel/src/logger.erl +++ b/lib/kernel/src/logger.erl @@ -43,11 +43,14 @@ get_module_level/0, get_module_level/1, set_primary_config/1, set_primary_config/2, set_handler_config/2, set_handler_config/3, + set_proxy_config/1, update_primary_config/1, update_handler_config/2, update_handler_config/3, + update_proxy_config/1, update_formatter_config/2, update_formatter_config/3, get_primary_config/0, get_handler_config/1, get_handler_config/0, get_handler_ids/0, get_config/0, + get_proxy_config/0, add_handlers/1]). %% Private configuration @@ -122,6 +125,18 @@ {filters,log | stop,[{filter_id(),filter()}]} | {module_level,level(),[module()]}]. +-type olp_config() :: #{sync_mode_qlen => non_neg_integer(), + drop_mode_qlen => pos_integer(), + flush_qlen => pos_integer(), + burst_limit_enable => boolean(), + burst_limit_max_count => pos_integer(), + burst_limit_window_time => pos_integer(), + overload_kill_enable => boolean(), + overload_kill_qlen => pos_integer(), + overload_kill_mem_size => pos_integer(), + overload_kill_restart_after => + non_neg_integer() | infinity}. + -export_type([log_event/0, level/0, report/0, @@ -137,7 +152,8 @@ filter_arg/0, filter_return/0, config_handler/0, - formatter_config/0]). + formatter_config/0, + olp_config/0]). %%%----------------------------------------------------------------- %%% API @@ -390,6 +406,7 @@ set_primary_config(Key,Value) -> set_primary_config(Config) -> logger_server:set_config(primary,Config). + -spec set_handler_config(HandlerId,level,Level) -> Return when HandlerId :: handler_id(), Level :: level() | all | none, @@ -419,6 +436,11 @@ set_handler_config(HandlerId,Key,Value) -> set_handler_config(HandlerId,Config) -> logger_server:set_config(HandlerId,Config). +-spec set_proxy_config(Config) -> ok | {error,term()} when + Config :: olp_config(). +set_proxy_config(Config) -> + logger_server:set_config(proxy,Config). + -spec update_primary_config(Config) -> ok | {error,term()} when Config :: primary_config(). update_primary_config(Config) -> @@ -453,6 +475,11 @@ update_handler_config(HandlerId,Key,Value) -> update_handler_config(HandlerId,Config) -> logger_server:update_config(HandlerId,Config). +-spec update_proxy_config(Config) -> ok | {error,term()} when + Config :: olp_config(). +update_proxy_config(Config) -> + logger_server:update_config(proxy,Config). + -spec get_primary_config() -> Config when Config :: primary_config(). get_primary_config() -> @@ -486,6 +513,12 @@ get_handler_ids() -> {ok,#{handlers:=HandlerIds}} = logger_config:get(?LOGGER_TABLE,primary), HandlerIds. +-spec get_proxy_config() -> Config when + Config :: olp_config(). +get_proxy_config() -> + {ok,Config} = logger_config:get(?LOGGER_TABLE,proxy), + Config. + -spec update_formatter_config(HandlerId,FormatterConfig) -> ok | {error,term()} when HandlerId :: handler_id(), @@ -606,10 +639,12 @@ unset_process_metadata() -> -spec get_config() -> #{primary=>primary_config(), handlers=>[handler_config()], + proxy=>olp_config(), module_levels=>[{module(),level() | all | none}]}. get_config() -> #{primary=>get_primary_config(), handlers=>get_handler_config(), + proxy=>get_proxy_config(), module_levels=>lists:keysort(1,get_module_level())}. -spec internal_init_logger() -> ok | {error,term()}. @@ -672,6 +707,17 @@ init_kernel_handlers(Env) -> %% This function is responsible for resolving the handler config %% and then starting the correct handlers. This is done after the %% kernel supervisor tree has been started as it needs the logger_sup. +add_handlers(kernel) -> + Env = get_logger_env(kernel), + case get_proxy_opts(Env) of + undefined -> + add_handlers(kernel,Env); + Opts -> + case set_proxy_config(Opts) of + ok -> add_handlers(kernel,Env); + {error, Reason} -> {error,{bad_proxy_config,Reason}} + end + end; add_handlers(App) when is_atom(App) -> add_handlers(App,get_logger_env(App)); add_handlers(HandlerConfig) -> @@ -729,6 +775,8 @@ check_logger_config(kernel,[{filters,_,_}|Env]) -> check_logger_config(kernel,Env); check_logger_config(kernel,[{module_level,_,_}|Env]) -> check_logger_config(kernel,Env); +check_logger_config(kernel,[{proxy,_}|Env]) -> + check_logger_config(kernel,Env); check_logger_config(_,Bad) -> throw(Bad). @@ -784,6 +832,13 @@ get_primary_filters(Env) -> _ -> throw({multiple_filters,Env}) end. +get_proxy_opts(Env) -> + case [P || P={proxy,_} <- Env] of + [{proxy,Opts}] -> Opts; + [] -> undefined; + _ -> throw({multiple_proxies,Env}) + end. + %% This function looks at the kernel logger environment %% and updates it so that the correct logger is configured init_default_config(Type,Env) when Type==standard_io; @@ -880,30 +935,30 @@ log_allowed(Location,Level,Msg,Meta0) when is_map(Meta0) -> maps:merge(Location,maps:merge(proc_meta(),Meta0))), case node(maps:get(gl,Meta)) of Node when Node=/=node() -> - log_remote(Node,Level,Msg,Meta), - do_log_allowed(Level,Msg,Meta); + log_remote(Node,Level,Msg,Meta); _ -> - do_log_allowed(Level,Msg,Meta) - end. + ok + end, + do_log_allowed(Level,Msg,Meta,tid()). -do_log_allowed(Level,{Format,Args}=Msg,Meta) +do_log_allowed(Level,{Format,Args}=Msg,Meta,Tid) when ?IS_LEVEL(Level), is_list(Format), is_list(Args), is_map(Meta) -> - logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},tid()); -do_log_allowed(Level,Report,Meta) + logger_backend:log_allowed(#{level=>Level,msg=>Msg,meta=>Meta},Tid); +do_log_allowed(Level,Report,Meta,Tid) when ?IS_LEVEL(Level), ?IS_REPORT(Report), is_map(Meta) -> logger_backend:log_allowed(#{level=>Level,msg=>{report,Report},meta=>Meta}, - tid()); -do_log_allowed(Level,String,Meta) + Tid); +do_log_allowed(Level,String,Meta,Tid) when ?IS_LEVEL(Level), ?IS_STRING(String), is_map(Meta) -> logger_backend:log_allowed(#{level=>Level,msg=>{string,String},meta=>Meta}, - tid()). + Tid). tid() -> ets:whereis(?LOGGER_TABLE). @@ -913,7 +968,7 @@ log_remote(Node,Level,Msg,Meta) -> log_remote(Node,{log,Level,Msg,Meta}). log_remote(Node,Request) -> - {logger,Node} ! Request, + logger_proxy:log({remote,Node,Request}), ok. add_default_metadata(Meta) -> diff --git a/lib/kernel/src/logger_config.erl b/lib/kernel/src/logger_config.erl index 6bfe658552..5024d20cfe 100644 --- a/lib/kernel/src/logger_config.erl +++ b/lib/kernel/src/logger_config.erl @@ -31,7 +31,9 @@ -include("logger_internal.hrl"). new(Name) -> - _ = ets:new(Name,[set,protected,named_table,{write_concurrency,true}]), + _ = ets:new(Name,[set,protected,named_table, + {read_concurrency,true}, + {write_concurrency,true}]), ets:whereis(Name). delete(Tid,Id) -> @@ -64,6 +66,8 @@ get(Tid,What) -> case ets:lookup(Tid,table_key(What)) of [{_,_,Config}] -> {ok,Config}; + [{_,Config}] when What=:=proxy -> + {ok,Config}; [] -> {error,{not_found,What}} end. @@ -77,10 +81,15 @@ get(Tid,What,Level) -> [Data] -> {ok,Data} end. +create(Tid,proxy,Config) -> + ets:insert(Tid,{table_key(proxy),Config}); create(Tid,What,Config) -> LevelInt = level_to_int(maps:get(level,Config)), ets:insert(Tid,{table_key(What),LevelInt,Config}). +set(Tid,proxy,Config) -> + ets:insert(Tid,{table_key(proxy),Config}), + ok; set(Tid,What,Config) -> LevelInt = level_to_int(maps:get(level,Config)), %% Should do this only if the level has actually changed. Possibly @@ -146,5 +155,6 @@ int_to_level(?LOG_ALL) -> all. %%%----------------------------------------------------------------- %%% Internal +table_key(proxy) -> ?PROXY_KEY; table_key(primary) -> ?PRIMARY_KEY; table_key(HandlerId) -> {?HANDLER_KEY,HandlerId}. diff --git a/lib/kernel/src/logger_disk_log_h.erl b/lib/kernel/src/logger_disk_log_h.erl index 2a81458ec8..47b39da900 100644 --- a/lib/kernel/src/logger_disk_log_h.erl +++ b/lib/kernel/src/logger_disk_log_h.erl @@ -19,124 +19,89 @@ %% -module(logger_disk_log_h). --behaviour(gen_server). - -include("logger.hrl"). -include("logger_internal.hrl"). -include("logger_h_common.hrl"). %%% API --export([start_link/3, info/1, filesync/1, reset/1]). +-export([filesync/1]). -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +%% logger_h_common callbacks +-export([init/2, check_config/4, reset_state/2, + filesync/3, write/4, handle_info/3, terminate/3]). %% logger callbacks -export([log/2, adding_handler/1, removing_handler/1, changing_config/3, filter_config/1]). -%% handler internal --export([log_handler_info/4]). - %%%=================================================================== %%% API %%%=================================================================== %%%----------------------------------------------------------------- -%%% Start a disk_log handler process and link to caller. -%%% This function is called by the kernel supervisor when this -%%% handler process gets added (as a result of calling add/3). --spec start_link(Name, Config, HandlerState) -> {ok,Pid} | {error,Reason} when - Name :: atom(), - Config :: logger:handler_config(), - HandlerState :: map(), - Pid :: pid(), - Reason :: term(). - -start_link(Name, Config, HandlerState) -> - proc_lib:start_link(?MODULE,init,[[Name,Config,HandlerState]]). - -%%%----------------------------------------------------------------- %%% -spec filesync(Name) -> ok | {error,Reason} when Name :: atom(), Reason :: handler_busy | {badarg,term()}. -filesync(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - disk_log_sync, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; filesync(Name) -> - {error,{badarg,{filesync,[Name]}}}. + logger_h_common:filesync(?MODULE,Name). + +%%%=================================================================== +%%% logger callbacks +%%%=================================================================== %%%----------------------------------------------------------------- -%%% --spec info(Name) -> Info | {error,Reason} when - Name :: atom(), - Info :: term(), - Reason :: handler_busy | {badarg,term()}. +%%% Handler being added +adding_handler(Config) -> + logger_h_common:adding_handler(Config). -info(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - info, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -info(Name) -> - {error,{badarg,{info,[Name]}}}. +%%%----------------------------------------------------------------- +%%% Updating handler config +changing_config(SetOrUpdate, OldConfig, NewConfig) -> + logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig). %%%----------------------------------------------------------------- -%%% --spec reset(Name) -> ok | {error,Reason} when - Name :: atom(), - Reason :: handler_busy | {badarg,term()}. +%%% Handler being removed +removing_handler(Config) -> + logger_h_common:removing_handler(Config). + +%%%----------------------------------------------------------------- +%%% Log a string or report +-spec log(LogEvent, Config) -> ok when + LogEvent :: logger:log_event(), + Config :: logger:handler_config(). -reset(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - reset, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -reset(Name) -> - {error,{badarg,{reset,[Name]}}}. +log(LogEvent, Config) -> + logger_h_common:log(LogEvent, Config). +%%%----------------------------------------------------------------- +%%% Remove internal fields from configuration +filter_config(Config) -> + logger_h_common:filter_config(Config). %%%=================================================================== -%%% logger callbacks +%%% logger_h_common callbacks %%%=================================================================== - -%%%----------------------------------------------------------------- -%%% Handler being added -adding_handler(#{id:=Name}=Config) -> - case check_config(adding, Config) of - {ok, #{config:=HConfig}=Config1} -> - %% create initial handler state by merging defaults with config - HState = maps:merge(get_init_state(), HConfig), - case logger_h_common:overload_levels_ok(HState) of - true -> - start(Name, Config1, HState); - false -> - #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = HState, - {error,{invalid_levels,{SMQL,DMQL,FQL}}} - end; +init(Name, #{file:=File,type:=Type,max_no_bytes:=MNB,max_no_files:=MNF}) -> + case open_disk_log(Name, File, Type, MNB, MNF) of + ok -> + {ok,#{log_opts => #{file => File, + type => Type, + max_no_bytes => MNB, + max_no_files => MNF}, + prev_log_result => ok, + prev_sync_result => ok, + prev_disk_log_info => undefined}}; Error -> Error end. -%%%----------------------------------------------------------------- -%%% Updating handler config -changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) -> +check_config(Name,set,undefined,HConfig0) -> + HConfig=merge_default_logopts(Name,maps:merge(get_default_config(),HConfig0)), + check_config(HConfig); +check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) -> WriteOnce = maps:with([type,file,max_no_files,max_no_bytes],OldHConfig), - ReadOnly = maps:with([handler_pid,mode_tab],OldHConfig), - NewHConfig0 = maps:get(config, NewConfig, #{}), Default = case SetOrUpdate of set -> @@ -146,66 +111,25 @@ changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) -> OldHConfig end, - %% Allow (accidentially) included read-only fields - just overwrite them - NewHConfig = maps:merge(maps:merge(Default,NewHConfig0),ReadOnly), + NewHConfig = maps:merge(Default,NewHConfig0), - %% But fail if write-once fields are changed + %% Fail if write-once fields are changed case maps:with([type,file,max_no_files,max_no_bytes],NewHConfig) of WriteOnce -> - changing_config1(maps:get(handler_pid,OldHConfig), - OldConfig, - NewConfig#{config=>NewHConfig}); + check_config(NewHConfig); Other -> {Old,New} = logger_server:diff_maps(WriteOnce,Other), - {error,{illegal_config_change,#{config=>Old},#{config=>New}}} - end. - -changing_config1(HPid, OldConfig, NewConfig) -> - case check_config(changing, NewConfig) of - Result = {ok,NewConfig1} -> - try gen_server:call(HPid, {change_config,OldConfig,NewConfig1}, - ?DEFAULT_CALL_TIMEOUT) of - ok -> Result; - Error -> Error - catch - _:{timeout,_} -> {error,handler_busy} - end; - Error -> - Error + {error,{illegal_config_change,?MODULE,Old,New}} end. -check_config(adding, #{id:=Name}=Config) -> - %% merge handler specific config data - HConfig1 = maps:get(config, Config, #{}), - HConfig2 = maps:merge(get_default_config(), HConfig1), - HConfig3 = merge_default_logopts(Name, HConfig2), - case check_h_config(maps:to_list(HConfig3)) of - ok -> - {ok,Config#{config=>HConfig3}}; - Error -> - Error - end; -check_config(changing, Config) -> - HConfig = maps:get(config, Config, #{}), +check_config(HConfig) -> case check_h_config(maps:to_list(HConfig)) of - ok -> {ok,Config}; - Error -> Error + ok -> + {ok,HConfig}; + {error,{Key,Value}} -> + {error,{invalid_config,?MODULE,#{Key=>Value}}} end. -merge_default_logopts(Name, HConfig) -> - Type = maps:get(type, HConfig, wrap), - {DefaultNoFiles,DefaultNoBytes} = - case Type of - halt -> {undefined,infinity}; - _wrap -> {10,1048576} - end, - {ok,Dir} = file:get_cwd(), - Defaults = #{file => filename:join(Dir,Name), - max_no_files => DefaultNoFiles, - max_no_bytes => DefaultNoBytes, - type => Type}, - maps:merge(Defaults, HConfig). - check_h_config([{file,File}|Config]) when is_list(File) -> check_h_config(Config); check_h_config([{max_no_files,undefined}|Config]) -> @@ -218,454 +142,59 @@ check_h_config([{max_no_bytes,N}|Config]) when is_integer(N), N>0 -> check_h_config(Config); check_h_config([{type,Type}|Config]) when Type==wrap; Type==halt -> check_h_config(Config); -check_h_config([Other | Config]) -> - case logger_h_common:check_common_config(Other) of - valid -> - check_h_config(Config); - invalid -> - {error,{invalid_config,?MODULE,Other}} - end; +check_h_config([Other | _]) -> + {error,Other}; check_h_config([]) -> ok. -%%%----------------------------------------------------------------- -%%% Handler being removed -removing_handler(#{id:=Name}) -> - stop(Name). - -%%%----------------------------------------------------------------- -%%% Log a string or report --spec log(LogEvent, Config) -> ok when - LogEvent :: logger:log_event(), - Config :: logger:handler_config(). - -log(LogEvent, Config = #{id := Name, - config := #{handler_pid := HPid, - mode_tab := ModeTab}}) -> - %% if the handler has crashed, we must drop this event - %% and hope the handler restarts so we can try again - true = is_process_alive(HPid), - Bin = logger_h_common:log_to_binary(LogEvent, Config), - logger_h_common:call_cast_or_drop(Name, HPid, ModeTab, Bin). +get_default_config() -> + #{}. -%%%----------------------------------------------------------------- -%%% Remove internal fields from configuration -filter_config(#{config:=HConfig}=Config) -> - Config#{config=>maps:without([handler_pid,mode_tab],HConfig)}. +merge_default_logopts(Name, HConfig) -> + Type = maps:get(type, HConfig, wrap), + {DefaultNoFiles,DefaultNoBytes} = + case Type of + halt -> {undefined,infinity}; + _wrap -> {10,1048576} + end, + {ok,Dir} = file:get_cwd(), + Defaults = #{file => filename:join(Dir,Name), + max_no_files => DefaultNoFiles, + max_no_bytes => DefaultNoBytes, + type => Type}, + maps:merge(Defaults, HConfig). -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== +filesync(Name,_Mode,State) -> + Result = ?disk_log_sync(Name), + maybe_notify_error(Name, filesync, Result, prev_sync_result, State). -init([Name, - Config = #{config := HConfig = #{file:=File, - type:=Type, - max_no_bytes:=MNB, - max_no_files:=MNF}}, - State = #{dl_sync_int := DLSyncInt}]) -> - - RegName = ?name_to_reg_name(?MODULE,Name), - register(RegName, self()), - process_flag(trap_exit, true), - process_flag(message_queue_data, off_heap), - - ?init_test_hooks(), - ?start_observation(Name), - - LogOpts = #{file=>File, type=>Type, max_no_bytes=>MNB, max_no_files=>MNF}, - case open_disk_log(Name, File, Type, MNB, MNF) of - ok -> - try ets:new(Name, [public]) of - ModeTab -> - ?set_mode(ModeTab, async), - T0 = ?timestamp(), - State1 = - ?merge_with_stats(State#{ - id => Name, - mode_tab => ModeTab, - mode => async, - dl_sync => DLSyncInt, - log_opts => LogOpts, - last_qlen => 0, - last_log_ts => T0, - burst_win_ts => T0, - burst_msg_count => 0, - last_op => sync, - prev_log_result => ok, - prev_sync_result => ok, - prev_disk_log_info => undefined}), - Config1 = - Config#{config => HConfig#{handler_pid => self(), - mode_tab => ModeTab}}, - proc_lib:init_ack({ok,self(),Config1}), - gen_server:cast(self(), repeated_disk_log_sync), - case logger_h_common:unset_restart_flag(Name, ?MODULE) of - true -> - %% inform about restart - gen_server:cast(self(), {log_handler_info, - "Handler ~p restarted", - [Name]}); - false -> - %% initial start - ok - end, - gen_server:enter_loop(?MODULE, [], State1) - catch - _:Error -> - unregister(RegName), - logger_h_common:error_notify({open_disk_log,Name,Error}), - proc_lib:init_ack(Error) - end; - Error -> - unregister(RegName), - logger_h_common:error_notify({open_disk_log,Name,Error}), - proc_lib:init_ack(Error) - end. +write(Name, Mode, Bin, State) -> + Result = ?disk_log_write(Name, Mode, Bin), + maybe_notify_error(Name, log, Result, prev_log_result, State). -%% This is the synchronous log event. -handle_call({log, Bin}, _From, State) -> - {Result,State1} = do_log(Bin, call, State), - %% Result == ok | dropped - {reply, Result, State1}; - -handle_call(disk_log_sync, _From, State = #{id := Name}) -> - State1 = #{prev_sync_result := Result} = disk_log_sync(Name, State), - {reply, Result, State1}; - -handle_call({change_config,_OldConfig,NewConfig}, _From, - State = #{filesync_repeat_interval := FSyncInt0}) -> - HConfig = maps:get(config, NewConfig, #{}), - State1 = #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = maps:merge(State, HConfig), - case logger_h_common:overload_levels_ok(State1) of - true -> - _ = - case maps:get(filesync_repeat_interval, HConfig, undefined) of - undefined -> - ok; - no_repeat -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)); - FSyncInt0 -> - ok; - _FSyncInt1 -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)), - _ = gen_server:cast(self(), repeated_disk_log_sync) - end, - {reply, ok, State1}; - false -> - {reply, {error,{invalid_levels,{SMQL,DMQL,FQL}}}, State} - end; - -handle_call(info, _From, State) -> - {reply, State, State}; - -handle_call(reset, _From, State) -> - State1 = ?merge_with_stats(State), - {reply, ok, State1#{last_qlen => 0, - last_log_ts => ?timestamp(), - prev_log_result => ok, - prev_sync_result => ok, - prev_disk_log_info => undefined}}; - -handle_call(stop, _From, State) -> - {stop, {shutdown,stopped}, ok, State}. - - -%% This is the asynchronous log event. -handle_cast({log, Bin}, State) -> - {_,State1} = do_log(Bin, cast, State), - {noreply, State1}; - -handle_cast({log_handler_info, Format, Args}, State = #{id:=Name}) -> - log_handler_info(Name, Format, Args, State), - {noreply, State}; - -%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this -%% clause gets called repeatedly by the handler. In order to -%% guarantee that a filesync *always* happens after the last log -%% event, the repeat operation must be active! -handle_cast(repeated_disk_log_sync, - State = #{id := Name, - filesync_repeat_interval := FSyncInt, - last_op := LastOp}) -> - State1 = - if is_integer(FSyncInt) -> - %% only do filesync if something has been - %% written since last time we checked - NewState = if LastOp == sync -> - State; - true -> - disk_log_sync(Name, State) - end, - {ok,TRef} = - timer:apply_after(FSyncInt, gen_server,cast, - [self(),repeated_disk_log_sync]), - NewState#{rep_sync_tref => TRef, last_op => sync}; - true -> - State - end, - {noreply,State1}. +reset_state(_Name, State) -> + State#{prev_log_result => ok, + prev_sync_result => ok, + prev_disk_log_info => undefined}. %% The disk log owner must handle status messages from disk_log. -handle_info({disk_log, _Node, _Log, {wrap,_NoLostItems}}, State) -> - {noreply, State}; -handle_info({disk_log, _Node, Log, Info = {truncated,_NoLostItems}}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}), - {noreply, State#{prev_disk_log_info => Info}}; -handle_info({disk_log, _Node, Log, Info = {blocked_log,_Items}}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}), - {noreply, State#{prev_disk_log_info => Info}}; -handle_info({disk_log, _Node, Log, full}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(full, PrevInfo, {disk_log,Name,Log,full}), - {noreply, State#{prev_disk_log_info => full}}; -handle_info({disk_log, _Node, Log, Info = {error_status,_Status}}, - State = #{id := Name, prev_disk_log_info := PrevInfo}) -> - error_notify_new(Info, PrevInfo, {disk_log,Name,Log,Info}), - {noreply, State#{prev_disk_log_info => Info}}; - -handle_info({'EXIT',_Pid,_Why}, State = #{id := _Name}) -> - {noreply, State}; - -handle_info(_, State) -> - {noreply, State}. - -terminate(Reason, State = #{id := Name}) -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, State, - undefined)), +handle_info(Name, {disk_log, _Node, Log, Info={truncated,_NoLostItems}}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(Name, {disk_log, _Node, Log, Info = {blocked_log,_Items}}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(Name, {disk_log, _Node, Log, Info = full}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(Name, {disk_log, _Node, Log, Info = {error_status,_Status}}, State) -> + maybe_notify_status(Name, Log, Info, prev_disk_log_info, State); +handle_info(_, _, State) -> + State. + +terminate(Name, _Reason, _State) -> _ = close_disk_log(Name, normal), - ok = logger_h_common:stop_or_restart(Name, Reason, State), - unregister(?name_to_reg_name(?MODULE, Name)), ok. -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - %%%----------------------------------------------------------------- %%% Internal functions - -%%%----------------------------------------------------------------- -%%% -get_default_config() -> - #{sync_mode_qlen => ?SYNC_MODE_QLEN, - drop_mode_qlen => ?DROP_MODE_QLEN, - flush_qlen => ?FLUSH_QLEN, - burst_limit_enable => ?BURST_LIMIT_ENABLE, - burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT, - burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME, - overload_kill_enable => ?OVERLOAD_KILL_ENABLE, - overload_kill_qlen => ?OVERLOAD_KILL_QLEN, - overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE, - overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}. - -get_init_state() -> - #{dl_sync_int => ?CONTROLLER_SYNC_INTERVAL, - filesync_ok_qlen => ?FILESYNC_OK_QLEN}. - -%%%----------------------------------------------------------------- -%%% Add a disk_log handler to the logger. -%%% This starts a dedicated handler process which should always -%%% exist if the handler is registered with logger (and should not -%%% exist if the handler is not registered). -%%% -%%% Config is the logger:handler_config() map. Handler specific parameters -%%% should be provided with a sub map associated with a key named -%%% 'config', e.g: -%%% -%%% Config = #{config => #{sync_mode_qlen => 50} -%%% -%%% The 'config' sub map will also contain parameters for configuring -%%% the disk_log: -%%% -%%% Config = #{config => #{file => file:filename(), -%%% max_no_bytes => integer(), -%%% max_no_files => integer(), -%%% type => wrap | halt}}. -%%% -%%% If type == halt, then max_no_files is ignored. -%%% -%%% The disk_log handler process is linked to logger_sup, which is -%%% part of the kernel application's supervision tree. -start(Name, Config, HandlerState) -> - LoggerDLH = - #{id => Name, - start => {?MODULE, start_link, [Name,Config,HandlerState]}, - restart => temporary, - shutdown => 2000, - type => worker, - modules => [?MODULE]}, - case supervisor:start_child(logger_sup, LoggerDLH) of - {ok,Pid,Config1} -> - ok = logger_handler_watcher:register_handler(Name,Pid), - {ok,Config1}; - Error -> - Error - end. - -%%%----------------------------------------------------------------- -%%% Stop and remove the handler. -stop(Name) -> - case whereis(?name_to_reg_name(?MODULE,Name)) of - undefined -> - ok; - Pid -> - %% We don't want to do supervisor:terminate_child here - %% since we need to distinguish this explicit stop from a - %% system termination in order to avoid circular attempts - %% at removing the handler (implying deadlocks and - %% timeouts). - %% And we don't need to do supervisor:delete_child, since - %% the restart type is temporary, which means that the - %% child specification is automatically removed from the - %% supervisor when the process dies. - _ = gen_server:call(Pid, stop), - ok - end. - -%%%----------------------------------------------------------------- -%%% Logging and overload control. --define(update_dl_sync(C, Interval), - if C == 0 -> Interval; - true -> C-1 end). - -%% check for overload between every event (and set Mode to async, -%% sync or drop accordingly), but never flush the whole mailbox -%% before LogWindowSize events have been handled -do_log(Bin, CallOrCast, State = #{id:=Name, mode := Mode0}) -> - T1 = ?timestamp(), - - %% check if the handler is getting overloaded, or if it's - %% recovering from overload (the check must be done for each - %% event to react quickly to large bursts of events and - %% to ensure that the handler can never end up in drop mode - %% with an empty mailbox, which would stop operation) - {Mode1,QLen,Mem,State1} = logger_h_common:check_load(State), - - if (Mode1 == drop) andalso (Mode0 =/= drop) -> - log_handler_info(Name, "Handler ~p switched to drop mode", - [Name], State); - (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) -> - log_handler_info(Name, "Handler ~p switched to ~w mode", - [Name,Mode1], State); - true -> - ok - end, - - %% kill the handler if it can't keep up with the load - logger_h_common:kill_if_choked(Name, QLen, Mem, ?MODULE, State), - - if Mode1 == flush -> - flush(Name, QLen, T1, State1); - true -> - write(Name, Mode1, T1, Bin, CallOrCast, State1) - end. - -%% this function is called by do_log/3 after an overload check -%% has been performed, where QLen > FlushQLen -flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) -> - %% flush messages in the mailbox (a limited number in - %% order to not cause long delays) - NewFlushed = logger_h_common:flush_log_events(?FLUSH_MAX_N), - - %% write info in log about flushed messages - log_handler_info(Name, "Handler ~p flushed ~w log events", - [Name,NewFlushed], State), - - %% because of the receive loop when flushing messages, the - %% handler will be scheduled out often and the mailbox could - %% grow very large, so we'd better check the queue again here - {_,_QLen1} = process_info(self(), message_queue_len), - ?observe(Name,{max_qlen,_QLen1}), - - %% Add 1 for the current log event - ?observe(Name,{flushed,NewFlushed+1}), - - State1 = ?update_max_time(?diff_time(T1,_T0),State), - {dropped,?update_other(flushed,FLUSHED,NewFlushed, - State1#{mode => ?set_mode(ModeTab,async), - last_qlen => 0, - last_log_ts => T1})}. - -%% this function is called to write to disk_log -write(Name, Mode, T1, Bin, _CallOrCast, - State = #{mode_tab := ModeTab, - dl_sync := DLSync, - dl_sync_int := DLSyncInt, - last_qlen := LastQLen, - last_log_ts := T0}) -> - %% check if we need to limit the number of writes - %% during a burst of log events - {DoWrite,BurstWinT,BurstMsgCount} = logger_h_common:limit_burst(State), - - %% only send a synhrounous event to the disk_log process - %% every DLSyncInt time, to give the handler time between - %% writes so it can keep up with incoming messages - {Status,LastQLen1,State1} = - if DoWrite, DLSync == 0 -> - ?observe(Name,{_CallOrCast,1}), - NewState = disk_log_write(Name, Bin, State), - {ok, element(2,process_info(self(),message_queue_len)), - NewState}; - DoWrite -> - ?observe(Name,{_CallOrCast,1}), - NewState = disk_log_write(Name, Bin, State), - {ok, LastQLen, NewState}; - not DoWrite -> - ?observe(Name,{flushed,1}), - {dropped, LastQLen, State} - end, - - %% Check if the time since the previous log event is long enough - - %% and the queue length small enough - to assume the mailbox has - %% been emptied, and if so, do filesync operation and reset mode to - %% async. Note that this is the best we can do to detect an idle - %% handler without setting a timer after each log call/cast. If the - %% time between two consecutive log events is fast and no new - %% event comes in after the last one, idle state won't be detected! - Time = ?diff_time(T1,T0), - {Mode1,BurstMsgCount1,State2} = - if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso - (Time > ?IDLE_DETECT_TIME_USEC) -> - {?change_mode(ModeTab,Mode,async), 0, disk_log_sync(Name,State1)}; - true -> - {Mode, BurstMsgCount,State1} - end, - - State3 = - ?update_calls_or_casts(_CallOrCast,1,State2), - State4 = - ?update_max_time(Time, - State3#{mode => Mode1, - last_qlen := LastQLen1, - last_log_ts => T1, - burst_win_ts => BurstWinT, - burst_msg_count => BurstMsgCount1, - dl_sync => ?update_dl_sync(DLSync,DLSyncInt)}), - {Status,State4}. - - -log_handler_info(Name, Format, Args, State) -> - Config = - case logger:get_handler_config(Name) of - {ok,Conf} -> Conf; - _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} - end, - Meta = #{time=>erlang:system_time(microsecond)}, - Bin = logger_h_common:log_to_binary(#{level => notice, - msg => {Format,Args}, - meta => Meta}, Config), - _ = disk_log_write(Name, Bin, State), - ok. - - open_disk_log(Name, File, Type, MaxNoBytes, MaxNoFiles) -> case filelib:ensure_dir(File) of ok -> @@ -698,43 +227,26 @@ close_disk_log(Name, _) -> _ = disk_log:lclose(Name), ok. -disk_log_write(Name, Bin, State) -> - case ?disk_log_blog(Name, Bin) of - ok -> - State#{prev_log_result => ok, last_op => write}; - LogError -> - _ = case maps:get(prev_log_result, State) of - LogError -> - %% don't report same error twice - ok; - _ -> - LogOpts = maps:get(log_opts, State), - logger_h_common:error_notify({Name,log, - LogOpts, - LogError}) - end, - State#{prev_log_result => LogError} - end. - -disk_log_sync(Name, State) -> - case ?disk_log_sync(Name) of - ok -> - State#{prev_sync_result => ok, last_op => sync}; - SyncError -> - _ = case maps:get(prev_sync_result, State) of - SyncError -> - %% don't report same error twice - ok; - _ -> - LogOpts = maps:get(log_opts, State), - logger_h_common:error_notify({Name,filesync, - LogOpts, - SyncError}) - end, - State#{prev_sync_result => SyncError} - end. +disk_log_write(Name, sync, Bin) -> + disk_log:blog(Name, Bin); +disk_log_write(Name, async, Bin) -> + disk_log:balog(Name, Bin). + +%%%----------------------------------------------------------------- +%%% Print error messages, but don't repeat the same message +maybe_notify_error(Name, Op, Result, Key, #{log_opts:=LogOpts}=State) -> + {Result,error_notify_new({Name, Op, LogOpts, Result}, Result, Key, State)}. + +maybe_notify_status(Name, Log, Info, Key, State) -> + error_notify_new({disk_log, Name, Log, Info}, Info, Key, State). -error_notify_new(Info,Info, _Term) -> +error_notify_new(Term, What, Key, State) -> + error_notify_new(What, maps:get(Key,State), Term), + State#{Key => What}. + +error_notify_new(ok,_Prev,_Term) -> + ok; +error_notify_new(Same,Same,_Term) -> ok; -error_notify_new(_Info0,_Info1, Term) -> +error_notify_new(_New,_Prev,Term) -> logger_h_common:error_notify(Term). diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl index 94c640cb92..e69f6de38d 100644 --- a/lib/kernel/src/logger_h_common.erl +++ b/lib/kernel/src/logger_h_common.erl @@ -18,26 +18,345 @@ %% %CopyrightEnd% %% -module(logger_h_common). +-behaviour(gen_server). -include("logger_h_common.hrl"). -include("logger_internal.hrl"). --export([log_to_binary/2, - check_common_config/1, - call_cast_or_drop/4, - check_load/1, - limit_burst/1, - kill_if_choked/5, - flush_log_events/0, - flush_log_events/1, - handler_exit/2, - set_restart_flag/2, - unset_restart_flag/2, - cancel_timer/1, - stop_or_restart/3, - overload_levels_ok/1, - error_notify/1, - info_notify/1]). +%% API +-export([filesync/2]). + +%% logger_olp callbacks +-export([init/1, handle_load/2, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3, notify/2, reset_state/1]). + +%% logger callbacks +-export([log/2, adding_handler/1, removing_handler/1, changing_config/3, + filter_config/1]). + +%% Library functions for handlers +-export([error_notify/1]). + +-define(OLP_KEYS,[sync_mode_qlen, + drop_mode_qlen, + flush_qlen, + burst_limit_enable, + burst_limit_max_count, + burst_limit_window_time, + overload_kill_enable, + overload_kill_qlen, + overload_kill_mem_size, + overload_kill_restart_after]). + +-define(COMMON_KEYS,[filesync_repeat_interval]). + +-define(READ_ONLY_KEYS,[olp]). + +%%%----------------------------------------------------------------- +%%% API + +%% This function is called by the logger_sup supervisor +filesync(Module, Name) -> + call(Module, Name, filesync). + +%%%----------------------------------------------------------------- +%%% Handler being added +adding_handler(#{id:=Name,module:=Module}=Config) -> + HConfig0 = maps:get(config, Config, #{}), + HandlerConfig0 = maps:without(?OLP_KEYS++?COMMON_KEYS,HConfig0), + case Module:check_config(Name,set,undefined,HandlerConfig0) of + {ok,HandlerConfig} -> + ModifiedCommon = maps:with(?COMMON_KEYS,HandlerConfig), + CommonConfig0 = maps:with(?COMMON_KEYS,HConfig0), + CommonConfig = maps:merge( + maps:merge(get_default_config(), CommonConfig0), + ModifiedCommon), + case check_config(CommonConfig) of + ok -> + HConfig = maps:merge(CommonConfig,HandlerConfig), + OlpOpts = maps:with(?OLP_KEYS,HConfig0), + start(OlpOpts, Config#{config => HConfig}); + {error,Faulty} -> + {error,{invalid_config,Module,Faulty}} + end; + Error -> + Error + end. + +%%%----------------------------------------------------------------- +%%% Handler being removed +removing_handler(#{id:=Name, module:=Module, config:=#{olp:=Olp}}) -> + case whereis(?name_to_reg_name(Module,Name)) of + undefined -> + ok; + _Pid -> + %% We don't want to do supervisor:terminate_child here + %% since we need to distinguish this explicit stop from a + %% system termination in order to avoid circular attempts + %% at removing the handler (implying deadlocks and + %% timeouts). + %% And we don't need to do supervisor:delete_child, since + %% the restart type is temporary, which means that the + %% child specification is automatically removed from the + %% supervisor when the process dies. + _ = logger_olp:stop(Olp), + ok + end. + +%%%----------------------------------------------------------------- +%%% Updating handler config +changing_config(SetOrUpdate, + #{id:=Name,config:=OldHConfig,module:=Module}, + NewConfig0) -> + NewHConfig0 = maps:get(config, NewConfig0, #{}), + NoHandlerKeys = ?OLP_KEYS++?COMMON_KEYS++?READ_ONLY_KEYS, + OldHandlerConfig = maps:without(NoHandlerKeys,OldHConfig), + NewHandlerConfig0 = maps:without(NoHandlerKeys,NewHConfig0), + case Module:check_config(Name, SetOrUpdate, + OldHandlerConfig,NewHandlerConfig0) of + {ok, NewHandlerConfig} -> + ModifiedCommon = maps:with(?COMMON_KEYS,NewHandlerConfig), + NewCommonConfig0 = maps:with(?COMMON_KEYS,NewHConfig0), + OldCommonConfig = maps:with(?COMMON_KEYS,OldHConfig), + CommonDefault = + case SetOrUpdate of + set -> + get_default_config(); + update -> + OldCommonConfig + end, + NewCommonConfig = maps:merge( + maps:merge(CommonDefault,NewCommonConfig0), + ModifiedCommon), + case check_config(NewCommonConfig) of + ok -> + OlpDefault = + case SetOrUpdate of + set -> + logger_olp:get_default_opts(); + update -> + maps:with(?OLP_KEYS,OldHConfig) + end, + Olp = maps:get(olp,OldHConfig), + NewOlpOpts = maps:merge(OlpDefault, + maps:with(?OLP_KEYS,NewHConfig0)), + case logger_olp:set_opts(Olp,NewOlpOpts) of + ok -> + maybe_set_repeated_filesync(Olp,OldCommonConfig, + NewCommonConfig), + ReadOnly = maps:with(?READ_ONLY_KEYS,OldHConfig), + NewHConfig = + maps:merge( + maps:merge( + maps:merge(NewCommonConfig,NewHandlerConfig), + ReadOnly), + NewOlpOpts), + NewConfig = NewConfig0#{config=>NewHConfig}, + {ok,NewConfig}; + Error -> + Error + end; + {error,Faulty} -> + {error,{invalid_config,Module,Faulty}} + end; + Error -> + Error + end. + +%%%----------------------------------------------------------------- +%%% Log a string or report +-spec log(LogEvent, Config) -> ok when + LogEvent :: logger:log_event(), + Config :: logger:handler_config(). + +log(LogEvent, Config = #{config := #{olp:=Olp}}) -> + %% if the handler has crashed, we must drop this event + %% and hope the handler restarts so we can try again + true = is_process_alive(logger_olp:get_pid(Olp)), + Bin = log_to_binary(LogEvent, Config), + logger_olp:load(Olp,Bin). + +%%%----------------------------------------------------------------- +%%% Remove internal fields from configuration +filter_config(#{config:=HConfig}=Config) -> + Config#{config=>maps:without(?READ_ONLY_KEYS,HConfig)}. + +%%%----------------------------------------------------------------- +%%% Start the handler process +%%% +%%% The process must always exist if the handler is registered with +%%% logger (and must not exist if the handler is not registered). +%%% +%%% The handler process is linked to logger_sup, which is part of the +%%% kernel application's supervision tree. +start(OlpOpts0, #{id := Name, module:=Module, config:=HConfig} = Config0) -> + RegName = ?name_to_reg_name(Module,Name), + ChildSpec = + #{id => Name, + start => {logger_olp, start_link, [RegName,?MODULE, + Config0, OlpOpts0]}, + restart => temporary, + shutdown => 2000, + type => worker, + modules => [?MODULE]}, + case supervisor:start_child(logger_sup, ChildSpec) of + {ok,Pid,Olp} -> + ok = logger_handler_watcher:register_handler(Name,Pid), + OlpOpts = logger_olp:get_opts(Olp), + {ok,Config0#{config=>(maps:merge(HConfig,OlpOpts))#{olp=>Olp}}}; + {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child -> + {error,Reason}; + Error -> + Error + end. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init(#{id := Name, module := Module, config := HConfig}) -> + process_flag(trap_exit, true), + + ?init_test_hooks(), + + case Module:init(Name, HConfig) of + {ok,HState} -> + %% Storing common config in state to avoid copying + %% (sending) the config data for each log message + CommonConfig = maps:with(?COMMON_KEYS,HConfig), + State = CommonConfig#{id => Name, + module => Module, + ctrl_sync_count => + ?CONTROLLER_SYNC_INTERVAL, + last_op => sync, + handler_state => HState}, + State1 = set_repeated_filesync(State), + {ok,State1}; + Error -> + Error + end. + +%% This is the log event. +handle_load(Bin, #{id:=Name, + module:=Module, + handler_state:=HandlerState, + ctrl_sync_count := CtrlSync}=State) -> + if CtrlSync==0 -> + {_,HS1} = Module:write(Name, sync, Bin, HandlerState), + State#{handler_state => HS1, + ctrl_sync_count => ?CONTROLLER_SYNC_INTERVAL, + last_op=>write}; + true -> + {_,HS1} = Module:write(Name, async, Bin, HandlerState), + State#{handler_state => HS1, + ctrl_sync_count => CtrlSync-1, + last_op=>write} + end. + +handle_call(filesync, _From, State = #{id := Name, + module := Module, + handler_state := HandlerState}) -> + {Result,HandlerState1} = Module:filesync(Name,sync,HandlerState), + {reply, Result, State#{handler_state=>HandlerState1, last_op=>sync}}. + +%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this +%% clause gets called repeatedly by the handler. In order to +%% guarantee that a filesync *always* happens after the last log +%% event, the repeat operation must be active! +handle_cast(repeated_filesync,State = #{filesync_repeat_interval := no_repeat}) -> + %% This clause handles a race condition which may occur when + %% config changes filesync_repeat_interval from an integer value + %% to no_repeat. + {noreply,State}; +handle_cast(repeated_filesync, + State = #{id := Name, + module := Module, + handler_state := HandlerState, + last_op := LastOp}) -> + State1 = + if LastOp == sync -> + State; + true -> + {_,HS} = Module:filesync(Name, async, HandlerState), + State#{handler_state => HS, last_op => sync} + end, + {noreply,set_repeated_filesync(State1)}; + +handle_cast({set_repeated_filesync,FSyncInt},State) -> + State1 = State#{filesync_repeat_interval=>FSyncInt}, + State2 = set_repeated_filesync(cancel_repeated_filesync(State1)), + {noreply, State2}. + +handle_info(Info, #{id := Name, module := Module, + handler_state := HandlerState} = State) -> + {noreply,State#{handler_state => Module:handle_info(Name,Info,HandlerState)}}. + +terminate(overloaded=Reason, #{id:=Name}=State) -> + _ = log_handler_info(Name,"Handler ~p overloaded and stopping",[Name],State), + do_terminate(Reason,State), + ConfigResult = logger:get_handler_config(Name), + case ConfigResult of + {ok,#{module:=Module}=HConfig0} -> + spawn(fun() -> logger:remove_handler(Name) end), + HConfig = try Module:filter_config(HConfig0) + catch _:_ -> HConfig0 + end, + {ok,fun() -> logger:add_handler(Name,Module,HConfig) end}; + Error -> + error_notify({Name,restart_impossible,Error}), + Error + end; +terminate(Reason, State) -> + do_terminate(Reason, State). + +do_terminate(Reason, State = #{id := Name, + module := Module, + handler_state := HandlerState}) -> + _ = cancel_repeated_filesync(State), + _ = Module:terminate(Name, Reason, HandlerState), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +reset_state(#{id:=Name, module:=Module, handler_state:=HandlerState} = State) -> + State#{handler_state=>Module:reset_state(Name, HandlerState)}. + +%%%----------------------------------------------------------------- +%%% Internal functions +call(Module, Name, Op) when is_atom(Name) -> + case logger_olp:call(?name_to_reg_name(Module,Name), Op) of + {error,busy} -> {error,handler_busy}; + Other -> Other + end; +call(_, Name, Op) -> + {error,{badarg,{Op,[Name]}}}. + +notify({mode_change,Mode0,Mode1},#{id:=Name}=State) -> + log_handler_info(Name,"Handler ~p switched from ~p to ~p mode", + [Name,Mode0,Mode1], State); +notify({flushed,Flushed},#{id:=Name}=State) -> + log_handler_info(Name, "Handler ~p flushed ~w log events", + [Name,Flushed], State); +notify(restart,#{id:=Name}=State) -> + log_handler_info(Name, "Handler ~p restarted", [Name], State); +notify(idle,#{id:=Name,module:=Module,handler_state:=HandlerState}=State) -> + {_,HS} = Module:filesync(Name,async,HandlerState), + State#{handler_state=>HS, last_op=>sync}. + +log_handler_info(Name, Format, Args, #{module:=Module, + handler_state:=HandlerState}=State) -> + Config = + case logger:get_handler_config(Name) of + {ok,Conf} -> Conf; + _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} + end, + Meta = #{time=>erlang:system_time(microsecond)}, + Bin = log_to_binary(#{level => notice, + msg => {Format,Args}, + meta => Meta}, Config), + {_,HS} = Module:write(Name, async, Bin, HandlerState), + State#{handler_state=>HS, last_op=>write}. %%%----------------------------------------------------------------- %%% Convert log data on any form to binary @@ -94,246 +413,44 @@ string_to_binary(String) -> throw(Error) end. - %%%----------------------------------------------------------------- %%% Check that the configuration term is valid -check_common_config({mode_tab,_Tid}) -> - valid; -check_common_config({handler_pid,Pid}) when is_pid(Pid) -> - valid; - -check_common_config({sync_mode_qlen,N}) when is_integer(N) -> - valid; -check_common_config({drop_mode_qlen,N}) when is_integer(N) -> - valid; -check_common_config({flush_qlen,N}) when is_integer(N) -> - valid; - -check_common_config({burst_limit_enable,Bool}) when Bool == true; - Bool == false -> - valid; -check_common_config({burst_limit_max_count,N}) when is_integer(N) -> - valid; -check_common_config({burst_limit_window_time,N}) when is_integer(N) -> - valid; - -check_common_config({overload_kill_enable,Bool}) when Bool == true; - Bool == false -> - valid; -check_common_config({overload_kill_qlen,N}) when is_integer(N) -> - valid; -check_common_config({overload_kill_mem_size,N}) when is_integer(N) -> - valid; -check_common_config({overload_kill_restart_after,NorA}) when is_integer(NorA); - NorA == infinity -> - valid; - -check_common_config({filesync_repeat_interval,NorA}) when is_integer(NorA); - NorA == no_repeat -> - valid; -check_common_config(_) -> - invalid. - +check_config(Config) when is_map(Config) -> + check_common_config(maps:to_list(Config)). -%%%----------------------------------------------------------------- -%%% Overload Protection -call_cast_or_drop(_Name, HandlerPid, ModeTab, Bin) -> - %% If the handler process is getting overloaded, the log event - %% will be synchronous instead of asynchronous (slows down the - %% logging tempo of a process doing lots of logging. If the - %% handler is choked, drop mode is set and no event will be sent. - try ?get_mode(ModeTab) of - async -> - gen_server:cast(HandlerPid, {log,Bin}); - sync -> - try gen_server:call(HandlerPid, {log,Bin}, ?DEFAULT_CALL_TIMEOUT) of - %% if return value from call == dropped, the - %% message has been flushed by handler and should - %% therefore not be counted as dropped in stats - ok -> ok; - dropped -> ok - catch - _:{timeout,_} -> - ?observe(_Name,{dropped,1}) - end; - drop -> - ?observe(_Name,{dropped,1}) - catch - %% if the ETS table doesn't exist (maybe because of a - %% handler restart), we can only drop the event - _:_ -> ?observe(_Name,{dropped,1}) - end, +check_common_config([{filesync_repeat_interval,NorA}|Config]) + when is_integer(NorA); NorA == no_repeat -> + check_common_config(Config); +check_common_config([{Key,Value}|_]) -> + {error,#{Key=>Value}}; +check_common_config([]) -> ok. -handler_exit(_Name, Reason) -> - exit(Reason). +get_default_config() -> + #{filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}. -set_restart_flag(Name, Module) -> - Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), - spawn(fun() -> - register(Flag, self()), - timer:sleep(infinity) - end), - ok. +set_repeated_filesync(#{filesync_repeat_interval:=FSyncInt} = State) + when is_integer(FSyncInt) -> + {ok,TRef} = timer:apply_after(FSyncInt, gen_server, cast, + [self(),repeated_filesync]), + State#{rep_sync_tref=>TRef}; +set_repeated_filesync(State) -> + State. -unset_restart_flag(Name, Module) -> - Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), - case whereis(Flag) of - undefined -> - false; - Pid -> - exit(Pid, kill), - true +cancel_repeated_filesync(State) -> + case maps:take(rep_sync_tref,State) of + {TRef,State1} -> + _ = timer:cancel(TRef), + State1; + error -> + State end. - -check_load(State = #{id:=_Name, mode_tab := ModeTab, mode := Mode, - sync_mode_qlen := SyncModeQLen, - drop_mode_qlen := DropModeQLen, - flush_qlen := FlushQLen}) -> - {_,Mem} = process_info(self(), memory), - ?observe(_Name,{max_mem,Mem}), - {_,QLen} = process_info(self(), message_queue_len), - ?observe(_Name,{max_qlen,QLen}), - %% When the handler process gets scheduled in, it's impossible - %% to predict the QLen. We could jump "up" arbitrarily from say - %% async to sync, async to drop, sync to flush, etc. However, when - %% the handler process manages the log events (without flushing), - %% one after the other, we will move "down" from drop to sync and - %% from sync to async. This way we don't risk getting stuck in - %% drop or sync mode with an empty mailbox. - {Mode1,_NewDrops,_NewFlushes} = - if - QLen >= FlushQLen -> - {flush, 0,1}; - QLen >= DropModeQLen -> - %% Note that drop mode will force log events to - %% be dropped on the client side (never sent get to - %% the handler). - IncDrops = if Mode == drop -> 0; true -> 1 end, - {?change_mode(ModeTab, Mode, drop), IncDrops,0}; - QLen >= SyncModeQLen -> - {?change_mode(ModeTab, Mode, sync), 0,0}; - true -> - {?change_mode(ModeTab, Mode, async), 0,0} - end, - State1 = ?update_other(drops,DROPS,_NewDrops,State), - {Mode1, QLen, Mem, - ?update_other(flushes,FLUSHES,_NewFlushes, - State1#{last_qlen => QLen})}. - -limit_burst(#{burst_limit_enable := false}) -> - {true,0,0}; -limit_burst(#{burst_win_ts := BurstWinT0, - burst_msg_count := BurstMsgCount, - burst_limit_window_time := BurstLimitWinTime, - burst_limit_max_count := BurstLimitMaxCnt}) -> - if (BurstMsgCount >= BurstLimitMaxCnt) -> - %% the limit for allowed messages has been reached - BurstWinT1 = ?timestamp(), - case ?diff_time(BurstWinT1,BurstWinT0) of - BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) -> - %% we're still within the burst time frame - {false,BurstWinT0,BurstMsgCount}; - _BurstCheckTime -> - %% burst time frame passed, reset counters - {true,BurstWinT1,0} - end; - true -> - %% the limit for allowed messages not yet reached - {true,BurstWinT0,BurstMsgCount+1} - end. - -kill_if_choked(Name, QLen, Mem, HandlerMod, - State = #{overload_kill_enable := KillIfOL, - overload_kill_qlen := OLKillQLen, - overload_kill_mem_size := OLKillMem}) -> - if KillIfOL andalso - ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) -> - HandlerMod:log_handler_info(Name, - "Handler ~p overloaded and stopping", - [Name], State), - set_restart_flag(Name, HandlerMod), - handler_exit(Name, {shutdown,{overloaded,Name,QLen,Mem}}); - true -> - ok - end. - -flush_log_events() -> - flush_log_events(-1). - -flush_log_events(Limit) -> - process_flag(priority, high), - Flushed = flush_log_events(0, Limit), - process_flag(priority, normal), - Flushed. - -flush_log_events(Limit, Limit) -> - Limit; -flush_log_events(N, Limit) -> - %% flush log events but leave other events, such as - %% filesync, info and change_config, so that these - %% have a chance to be processed even under heavy load - receive - {'$gen_cast',{log,_}} -> - flush_log_events(N+1, Limit); - {'$gen_call',{Pid,MRef},{log,_}} -> - Pid ! {MRef, dropped}, - flush_log_events(N+1, Limit) - after - 0 -> N - end. - -cancel_timer(TRef) when is_atom(TRef) -> ok; -cancel_timer(TRef) -> timer:cancel(TRef). - - -stop_or_restart(Name, {shutdown,Reason={overloaded,_Name,_QLen,_Mem}}, - #{overload_kill_restart_after := RestartAfter}) -> - %% If we're terminating because of an overload situation (see - %% logger_h_common:kill_if_choked/4), we need to remove the handler - %% and set a restart timer. A separate process must perform this - %% in order to avoid deadlock. - HandlerPid = self(), - ConfigResult = logger:get_handler_config(Name), - RemoveAndRestart = - fun() -> - MRef = erlang:monitor(process, HandlerPid), - receive - {'DOWN',MRef,_,_,_} -> - ok - after 30000 -> - error_notify(Reason), - exit(HandlerPid, kill) - end, - case ConfigResult of - {ok,#{module:=HMod}=HConfig0} when is_integer(RestartAfter) -> - _ = logger:remove_handler(Name), - HConfig = try HMod:filter_config(HConfig0) - catch _:_ -> HConfig0 - end, - _ = timer:apply_after(RestartAfter, logger, add_handler, - [Name,HMod,HConfig]); - {ok,_} -> - _ = logger:remove_handler(Name); - {error,CfgReason} when is_integer(RestartAfter) -> - error_notify({Name,restart_impossible,CfgReason}); - {error,_} -> - ok - end - end, - spawn(RemoveAndRestart), - ok; -stop_or_restart(_Name, _Reason, _State) -> - ok. - -overload_levels_ok(HandlerConfig) -> - SMQL = maps:get(sync_mode_qlen, HandlerConfig, ?SYNC_MODE_QLEN), - DMQL = maps:get(drop_mode_qlen, HandlerConfig, ?DROP_MODE_QLEN), - FQL = maps:get(flush_qlen, HandlerConfig, ?FLUSH_QLEN), - (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL). - error_notify(Term) -> ?internal_log(error, Term). -info_notify(Term) -> - ?internal_log(info, Term). +maybe_set_repeated_filesync(_Olp, + #{filesync_repeat_interval:=FSyncInt}, + #{filesync_repeat_interval:=FSyncInt}) -> + ok; +maybe_set_repeated_filesync(Olp,_,#{filesync_repeat_interval:=FSyncInt}) -> + logger_olp:cast(Olp,{set_repeated_filesync,FSyncInt}). diff --git a/lib/kernel/src/logger_h_common.hrl b/lib/kernel/src/logger_h_common.hrl index e0a7b6e3ca..004a61d9d9 100644 --- a/lib/kernel/src/logger_h_common.hrl +++ b/lib/kernel/src/logger_h_common.hrl @@ -1,50 +1,22 @@ - -%%%----------------------------------------------------------------- -%%% Overload protection configuration - -%%! *** NOTE *** -%%! It's important that: -%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN -%%! and that DROP_MODE_QLEN >= 2. -%%! Otherwise the handler could end up in drop mode with no new -%%! log requests to process. This would cause all future requests -%%! to be dropped (no switch to async mode would ever take place). - -%% This specifies the message_queue_len value where the log -%% requests switch from asynchronous casts to synchronous calls. --define(SYNC_MODE_QLEN, 10). -%% Above this message_queue_len, log requests will be dropped, -%% i.e. no log requests get sent to the handler process. --define(DROP_MODE_QLEN, 200). -%% Above this message_queue_len, the handler process will flush -%% its mailbox and only leave this number of messages in it. --define(FLUSH_QLEN, 1000). - -%% Never flush more than this number of messages in one go, -%% or the handler will be unresponsive for seconds (keep this -%% number as large as possible or the mailbox could grow large). --define(FLUSH_MAX_N, 5000). - -%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed -%% to be written within a BURST_LIMIT_WINDOW_TIME time frame. --define(BURST_LIMIT_ENABLE, true). --define(BURST_LIMIT_MAX_COUNT, 500). --define(BURST_LIMIT_WINDOW_TIME, 1000). - -%% This enables/disables the feature to automatically get the -%% handler terminated if it gets too loaded (and can't keep up). --define(OVERLOAD_KILL_ENABLE, false). -%% If the message_queue_len goes above this size even after -%% flushing has been performed, the handler is terminated. --define(OVERLOAD_KILL_QLEN, 20000). -%% If the memory usage exceeds this level --define(OVERLOAD_KILL_MEM_SIZE, 3000000). - -%% This is the default time that the handler will wait before -%% restarting and accepting new requests. The value 'infinity' -%% disables restarts. --define(OVERLOAD_KILL_RESTART_AFTER, 5000). -%%-define(OVERLOAD_KILL_RESTART_AFTER, infinity). +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2015. 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% +%% %% The handler sends asynchronous write requests to the process %% controlling the i/o device, but every once in this interval @@ -65,12 +37,6 @@ -define(FILESYNC_REPEAT_INTERVAL, 5000). %%-define(FILESYNC_REPEAT_INTERVAL, no_repeat). -%% This is the time after last message received that we think/hope -%% that the handler has an empty mailbox (no new log request has -%% come in). --define(IDLE_DETECT_TIME_MSEC, 100). --define(IDLE_DETECT_TIME_USEC, 100000). - %% Default disk log option values -define(DISK_LOG_TYPE, wrap). -define(DISK_LOG_MAX_NO_FILES, 10). @@ -83,43 +49,6 @@ list_to_atom(lists:concat([MODULE,"_",Name]))). %%%----------------------------------------------------------------- -%%% Overload protection macros - --define(timestamp(), erlang:monotonic_time(microsecond)). - --define(get_mode(Tid), - case ets:lookup(Tid, mode) of - [{mode,M}] -> M; - _ -> async - end). - --define(set_mode(Tid, M), - begin ets:insert(Tid, {mode,M}), M end). - --define(change_mode(Tid, M0, M1), - if M0 == M1 -> - M0; - true -> - ets:insert(Tid, {mode,M1}), - M1 - end). - --define(min(X1, X2), - if X2 == undefined -> X1; - X2 < X1 -> X2; - true -> X1 - end). - --define(max(X1, X2), - if - X2 == undefined -> X1; - X2 > X1 -> X2; - true -> X1 - end). - --define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0). - -%%%----------------------------------------------------------------- %%% The test hook macros make it possible to observe and manipulate %%% internal handler functionality. When enabled, these macros will %%% slow down execution and therefore should not be include in code @@ -137,7 +66,7 @@ ets:insert(?TEST_HOOKS_TAB, {internal_log,{logger,internal_log}}), ets:insert(?TEST_HOOKS_TAB, {file_write,ok}), ets:insert(?TEST_HOOKS_TAB, {file_datasync,ok}), - ets:insert(?TEST_HOOKS_TAB, {disk_log_blog,ok}), + ets:insert(?TEST_HOOKS_TAB, {disk_log_write,ok}), ets:insert(?TEST_HOOKS_TAB, {disk_log_sync,ok})). -define(set_internal_log(MOD_FUNC), @@ -150,7 +79,7 @@ ets:insert(?TEST_HOOKS_TAB, {internal_log,{logger,internal_log}}), ets:insert(?TEST_HOOKS_TAB, {file_write,ok}), ets:insert(?TEST_HOOKS_TAB, {file_datasync,ok}), - ets:insert(?TEST_HOOKS_TAB, {disk_log_blog,ok}), + ets:insert(?TEST_HOOKS_TAB, {disk_log_write,ok}), ets:insert(?TEST_HOOKS_TAB, {disk_log_sync,ok})). -define(internal_log(TYPE, TERM), @@ -171,11 +100,11 @@ [{_,ERROR}] -> ERROR catch _:_ -> file:datasync(DEVICE) end). - -define(disk_log_blog(LOG, DATA), - try ets:lookup(?TEST_HOOKS_TAB, disk_log_blog) of - [{_,ok}] -> disk_log:blog(LOG, DATA); + -define(disk_log_write(LOG, MODE, DATA), + try ets:lookup(?TEST_HOOKS_TAB, disk_log_write) of + [{_,ok}] -> disk_log_write(LOG, MODE, DATA); [{_,ERROR}] -> ERROR - catch _:_ -> disk_log:blog(LOG, DATA) end). + catch _:_ -> disk_log_write(LOG, MODE, DATA) end). -define(disk_log_sync(LOG), try ets:lookup(?TEST_HOOKS_TAB, disk_log_sync) of @@ -183,7 +112,6 @@ [{_,ERROR}] -> ERROR catch _:_ -> disk_log:sync(LOG) end). - -define(DEFAULT_CALL_TIMEOUT, 5000). -else. % DEFAULTS! -define(TEST_HOOKS_TAB, undefined). @@ -194,70 +122,6 @@ -define(internal_log(TYPE, TERM), logger:internal_log(TYPE, TERM)). -define(file_write(DEVICE, DATA), file:write(DEVICE, DATA)). -define(file_datasync(DEVICE), file:datasync(DEVICE)). - -define(disk_log_blog(LOG, DATA), disk_log:blog(LOG, DATA)). + -define(disk_log_write(LOG, MODE, DATA), disk_log_write(LOG, MODE, DATA)). -define(disk_log_sync(LOG), disk_log:sync(LOG)). - -define(DEFAULT_CALL_TIMEOUT, 10000). --endif. - -%%%----------------------------------------------------------------- -%%% These macros enable statistics counters in the state of the -%%% handler which is useful for analysing the overload protection -%%% behaviour. These counters should not be included in code to be -%%% officially released (as some counters will grow very large -%%% over time). - -%%-define(SAVE_STATS, true). --ifdef(SAVE_STATS). - -define(merge_with_stats(STATE), - STATE#{flushes => 0, flushed => 0, drops => 0, - casts => 0, calls => 0, - max_qlen => 0, max_time => 0}). - - -define(update_max_qlen(QLEN, STATE), - begin #{max_qlen := QLEN0} = STATE, - STATE#{max_qlen => ?max(QLEN0,QLEN)} end). - - -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE), - case CALL_OR_CAST of - cast -> - #{casts := CASTS0} = STATE, - STATE#{casts => CASTS0+INC}; - call -> - #{calls := CALLS0} = STATE, - STATE#{calls => CALLS0+INC} - end). - - -define(update_max_time(TIME, STATE), - begin #{max_time := TIME0} = STATE, - STATE#{max_time => ?max(TIME0,TIME)} end). - - -define(update_other(OTHER, VAR, INCVAL, STATE), - begin #{OTHER := VAR} = STATE, - STATE#{OTHER => VAR+INCVAL} end). - --else. % DEFAULT! - -define(merge_with_stats(STATE), STATE). - -define(update_max_qlen(_QLEN, STATE), STATE). - -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE). - -define(update_max_time(_TIME, STATE), STATE). - -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE). --endif. - -%%%----------------------------------------------------------------- -%%% These macros enable callbacks that make it possible to analyse -%%% the overload protection behaviour from outside the handler -%%% process (including dropped requests on the client side). -%%% An external callback module (?OBSERVER_MOD) is required which -%%% is not part of the kernel application. For this reason, these -%%% callbacks should not be included in code to be officially released. - -%%-define(OBSERVER_MOD, logger_test). --ifdef(OBSERVER_MOD). - -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)). - -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)). - --else. % DEFAULT! - -define(start_observation(_NAME), ok). - -define(observe(_NAME,_EVENT), ok). -endif. -%%! <--- diff --git a/lib/kernel/src/logger_internal.hrl b/lib/kernel/src/logger_internal.hrl index d96a4ac78b..e53922e5d3 100644 --- a/lib/kernel/src/logger_internal.hrl +++ b/lib/kernel/src/logger_internal.hrl @@ -19,6 +19,7 @@ %% -include_lib("kernel/include/logger.hrl"). -define(LOGGER_TABLE,logger). +-define(PROXY_KEY,'$proxy_config$'). -define(PRIMARY_KEY,'$primary_config$'). -define(HANDLER_KEY,'$handler_config$'). -define(LOGGER_META_KEY,'$logger_metadata$'). @@ -40,12 +41,14 @@ -define(DEFAULT_LOGGER_CALL_TIMEOUT, infinity). --define(LOG_INTERNAL(Level,Report), +-define(LOG_INTERNAL(Level,Report),?DO_LOG_INTERNAL(Level,[Report])). +-define(LOG_INTERNAL(Level,Format,Args),?DO_LOG_INTERNAL(Level,[Format,Args])). +-define(DO_LOG_INTERNAL(Level,Data), case logger:allow(Level,?MODULE) of true -> %% Spawn this to avoid deadlocks - _ = spawn(logger,macro_log,[?LOCATION,Level,Report, - logger:add_default_metadata(#{})]), + _ = spawn(logger,macro_log,[?LOCATION,Level|Data]++ + [logger:add_default_metadata(#{})]), ok; false -> ok diff --git a/lib/kernel/src/logger_olp.erl b/lib/kernel/src/logger_olp.erl new file mode 100644 index 0000000000..009280a9c9 --- /dev/null +++ b/lib/kernel/src/logger_olp.erl @@ -0,0 +1,626 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(logger_olp). +-behaviour(gen_server). + +-include("logger_olp.hrl"). +-include("logger_internal.hrl"). + +%% API +-export([start_link/4, load/2, info/1, reset/1, stop/1, restart/1, + set_opts/2, get_opts/1, get_default_opts/0, get_pid/1, + call/2, cast/2, get_ref/0, get_ref/1]). + +%% gen_server and proc_lib callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(OPT_KEYS,[sync_mode_qlen, + drop_mode_qlen, + flush_qlen, + burst_limit_enable, + burst_limit_max_count, + burst_limit_window_time, + overload_kill_enable, + overload_kill_qlen, + overload_kill_mem_size, + overload_kill_restart_after]). + +-export_type([olp_ref/0, options/0]). + +-opaque olp_ref() :: {atom(),pid(),ets:tid()}. + +-type options() :: logger:olp_config(). + +%%%----------------------------------------------------------------- +%%% API + +-spec start_link(Name,Module,Args,Options) -> {ok,Pid,Olp} | {error,Reason} when + Name :: atom(), + Module :: module(), + Args :: term(), + Options :: options(), + Pid :: pid(), + Olp :: olp_ref(), + Reason :: term(). +start_link(Name,Module,Args,Options0) when is_map(Options0) -> + Options = maps:merge(get_default_opts(),Options0), + case check_opts(Options) of + ok -> + proc_lib:start_link(?MODULE,init,[[Name,Module,Args,Options]]); + Error -> + Error + end. + +-spec load(Olp, Msg) -> ok when + Olp :: olp_ref(), + Msg :: term(). +load({_Name,Pid,ModeRef},Msg) -> + %% If the process is getting overloaded, the message will be + %% synchronous instead of asynchronous (slows down the tempo of a + %% process causing much load). If the process is choked, drop mode + %% is set and no message is sent. + try ?get_mode(ModeRef) of + async -> + gen_server:cast(Pid, {'$olp_load',Msg}); + sync -> + case call(Pid, {'$olp_load',Msg}) of + ok -> + ok; + _Other -> + %% dropped or {error,busy} + ?observe(_Name,{dropped,1}), + ok + end; + drop -> + ?observe(_Name,{dropped,1}) + catch + %% if the ETS table doesn't exist (maybe because of a + %% process restart), we can only drop the event + _:_ -> ?observe(_Name,{dropped,1}) + end, + ok. + +-spec info(Olp) -> map() | {error, busy} when + Olp :: atom() | pid() | olp_ref(). +info(Olp) -> + call(Olp, info). + +-spec reset(Olp) -> ok | {error, busy} when + Olp :: atom() | pid() | olp_ref(). +reset(Olp) -> + call(Olp, reset). + +-spec stop(Olp) -> ok when + Olp :: atom() | pid() | olp_ref(). +stop({_Name,Pid,_ModRef}) -> + stop(Pid); +stop(Pid) -> + _ = gen_server:call(Pid, stop), + ok. + +-spec set_opts(Olp, Opts) -> ok | {error,term()} | {error, busy} when + Olp :: atom() | pid() | olp_ref(), + Opts :: options(). +set_opts(Olp, Opts) -> + call(Olp, {set_opts,Opts}). + +-spec get_opts(Olp) -> options() | {error, busy} when + Olp :: atom() | pid() | olp_ref(). +get_opts(Olp) -> + call(Olp, get_opts). + +-spec get_default_opts() -> options(). +get_default_opts() -> + #{sync_mode_qlen => ?SYNC_MODE_QLEN, + drop_mode_qlen => ?DROP_MODE_QLEN, + flush_qlen => ?FLUSH_QLEN, + burst_limit_enable => ?BURST_LIMIT_ENABLE, + burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT, + burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME, + overload_kill_enable => ?OVERLOAD_KILL_ENABLE, + overload_kill_qlen => ?OVERLOAD_KILL_QLEN, + overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE, + overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER}. + +-spec restart(fun(() -> any())) -> ok. +restart(Fun) -> + Result = + try Fun() + catch C:R:S -> + {error,{restart_failed,Fun,C,R,S}} + end, + ?LOG_INTERNAL(debug,[{logger_olp,restart}, + {result,Result}]), + ok. + +-spec get_ref() -> olp_ref(). +get_ref() -> + get(olp_ref). + +-spec get_ref(PidOrName) -> olp_ref() | {error, busy} when + PidOrName :: pid() | atom(). +get_ref(PidOrName) -> + call(PidOrName,get_ref). + +-spec get_pid(olp_ref()) -> pid(). +get_pid({_Name,Pid,_ModeRef}) -> + Pid. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([Name,Module,Args,Options]) -> + register(Name, self()), + process_flag(message_queue_data, off_heap), + + ?start_observation(Name), + + try ets:new(Name, [public]) of + ModeRef -> + OlpRef = {Name,self(),ModeRef}, + put(olp_ref,OlpRef), + try Module:init(Args) of + {ok,CBState} -> + ?set_mode(ModeRef, async), + T0 = ?timestamp(), + proc_lib:init_ack({ok,self(),OlpRef}), + %% Storing options in state to avoid copying + %% (sending) the option data with each message + State0 = ?merge_with_stats( + Options#{id => Name, + idle=> true, + module => Module, + mode_ref => ModeRef, + mode => async, + last_qlen => 0, + last_load_ts => T0, + burst_win_ts => T0, + burst_msg_count => 0, + cb_state => CBState}), + State = reset_restart_flag(State0), + gen_server:enter_loop(?MODULE, [], State); + Error -> + _ = ets:delete(ModeRef), + unregister(Name), + proc_lib:init_ack(Error) + catch + _:Error -> + _ = ets:delete(ModeRef), + unregister(Name), + proc_lib:init_ack(Error) + end + catch + _:Error -> + unregister(Name), + proc_lib:init_ack(Error) + end. + +%% This is the synchronous load event. +handle_call({'$olp_load', Msg}, _From, State) -> + {Result,State1} = do_load(Msg, call, State#{idle=>false}), + %% Result == ok | dropped + reply_return(Result,State1); + +handle_call(get_ref,_From,#{id:=Name,mode_ref:=ModeRef}=State) -> + reply_return({Name,self(),ModeRef},State); + +handle_call({set_opts,Opts0},_From,State) -> + Opts = maps:merge(maps:with(?OPT_KEYS,State),Opts0), + case check_opts(Opts) of + ok -> + reply_return(ok, maps:merge(State,Opts)); + Error -> + reply_return(Error, State) + end; + +handle_call(get_opts,_From,State) -> + reply_return(maps:with(?OPT_KEYS,State), State); + +handle_call(info, _From, State) -> + reply_return(State, State); + +handle_call(reset, _From, #{module:=Module,cb_state:=CBState}=State) -> + State1 = ?merge_with_stats(State), + CBState1 = try_callback_call(Module,reset_state,[CBState],CBState), + reply_return(ok, State1#{idle => true, + last_qlen => 0, + last_load_ts => ?timestamp(), + cb_state => CBState1}); + +handle_call(stop, _From, State) -> + {stop, {shutdown,stopped}, ok, State}; + +handle_call(Msg, From, #{module:=Module,cb_state:=CBState}=State) -> + case try_callback_call(Module,handle_call,[Msg, From, CBState]) of + {reply,Reply,CBState1} -> + reply_return(Reply,State#{cb_state=>CBState1}); + {noreply,CBState1} -> + noreply_return(State#{cb_state=>CBState1}); + {stop, Reason, Reply, CBState1} -> + {stop, Reason, Reply, State#{cb_state=>CBState1}}; + {stop, Reason, CBState1} -> + {stop, Reason, State#{cb_state=>CBState1}} + end. + +%% This is the asynchronous load event. +handle_cast({'$olp_load', Msg}, State) -> + {_Result,State1} = do_load(Msg, cast, State#{idle=>false}), + noreply_return(State1); + +handle_cast(Msg, #{module:=Module, cb_state:=CBState} = State) -> + case try_callback_call(Module,handle_cast,[Msg, CBState]) of + {noreply,CBState1} -> + noreply_return(State#{cb_state=>CBState1}); + {stop, Reason, CBState1} -> + {stop, Reason, State#{cb_state=>CBState1}} + end. + +handle_info(timeout, #{mode_ref:=_ModeRef, mode:=Mode} = State) -> + State1 = notify(idle,State), + State2 = maybe_notify_mode_change(async,State1), + {noreply, State2#{idle => true, + mode => ?change_mode(_ModeRef, Mode, async), + burst_msg_count => 0}}; +handle_info(Msg, #{module := Module, cb_state := CBState} = State) -> + case try_callback_call(Module,handle_info,[Msg, CBState]) of + {noreply,CBState1} -> + noreply_return(State#{cb_state=>CBState1}); + {stop, Reason, CBState1} -> + {stop, Reason, State#{cb_state=>CBState1}}; + {load,CBState1} -> + {_,State1} = do_load(Msg, cast, State#{idle=>false, + cb_state=>CBState1}), + noreply_return(State1) + end. + +terminate({shutdown,{overloaded,_QLen,_Mem}}, + #{id:=Name, module := Module, cb_state := CBState, + overload_kill_restart_after := RestartAfter} = State) -> + %% We're terminating because of an overload situation (see + %% kill_if_choked/3). + unregister(Name), %%!!!! to avoid error printout of callback crashed on stop + case try_callback_call(Module,terminate,[overloaded,CBState],ok) of + {ok,Fun} when is_function(Fun,0), is_integer(RestartAfter) -> + set_restart_flag(State), + _ = timer:apply_after(RestartAfter,?MODULE,restart,[Fun]), + ok; + _ -> + ok + end; +terminate(Reason, #{id:=Name, module:=Module, cb_state:=CBState}) -> + _ = try_callback_call(Module,terminate,[Reason,CBState],ok), + unregister(Name), + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + + +%%%----------------------------------------------------------------- +%%% Internal functions +-spec call(Olp, term()) -> term() | {error,busy} when + Olp :: atom() | pid() | olp_ref(). +call({_Name, Pid, _ModeRef},Msg) -> + call(Pid, Msg); +call(Server, Msg) -> + try + gen_server:call(Server, Msg) + catch + _:{timeout,_} -> {error,busy} + end. + +-spec cast(olp_ref(),term()) -> ok. +cast({_Name, Pid, _ModeRef},Msg) -> + gen_server:cast(Pid, Msg). + +%% check for overload between every event (and set Mode to async, +%% sync or drop accordingly), but never flush the whole mailbox +%% before LogWindowSize events have been handled +do_load(Msg, CallOrCast, State) -> + T1 = ?timestamp(), + State1 = ?update_time(T1,State), + + %% check if the process is getting overloaded, or if it's + %% recovering from overload (the check must be done for each + %% event to react quickly to large bursts of events and + %% to ensure that the handler can never end up in drop mode + %% with an empty mailbox, which would stop operation) + {Mode1,QLen,Mem,State2} = check_load(State1), + + %% kill the handler if it can't keep up with the load + kill_if_choked(QLen, Mem, State2), + + if Mode1 == flush -> + flush(T1, State2); + true -> + handle_load(Mode1, T1, Msg, CallOrCast, State2) + end. + +%% this function is called by do_load/3 after an overload check +%% has been performed, where QLen > FlushQLen +flush(T1, State=#{id := _Name, mode := Mode, last_load_ts := _T0, mode_ref := ModeRef}) -> + %% flush load messages in the mailbox (a limited number in order + %% to not cause long delays) + NewFlushed = flush_load(?FLUSH_MAX_N), + + %% write info in log about flushed messages + State1=notify({flushed,NewFlushed},State), + + %% because of the receive loop when flushing messages, the + %% handler will be scheduled out often and the mailbox could + %% grow very large, so we'd better check the queue again here + {_,QLen1} = process_info(self(), message_queue_len), + ?observe(_Name,{max_qlen,QLen1}), + + %% Add 1 for the current log event + ?observe(_Name,{flushed,NewFlushed+1}), + + State2 = ?update_max_time(?diff_time(T1,_T0),State1), + State3 = ?update_max_qlen(QLen1,State2), + State4 = maybe_notify_mode_change(async,State3), + {dropped,?update_other(flushed,FLUSHED,NewFlushed, + State4#{mode => ?change_mode(ModeRef,Mode,async), + last_qlen => QLen1, + last_load_ts => T1})}. + +%% this function is called to actually handle the message +handle_load(Mode, T1, Msg, _CallOrCast, + State = #{id := _Name, + module := Module, + cb_state := CBState, + last_qlen := LastQLen, + last_load_ts := _T0}) -> + %% check if we need to limit the number of writes + %% during a burst of log events + {DoWrite,State1} = limit_burst(State), + + {Result,LastQLen1,CBState1} = + if DoWrite -> + ?observe(_Name,{_CallOrCast,1}), + CBS = try_callback_call(Module,handle_load,[Msg,CBState]), + {ok,element(2, process_info(self(), message_queue_len)),CBS}; + true -> + ?observe(_Name,{flushed,1}), + {dropped,LastQLen,CBState} + end, + State2 = State1#{cb_state=>CBState1}, + + State3 = State2#{mode => Mode}, + State4 = ?update_calls_or_casts(_CallOrCast,1,State3), + State5 = ?update_max_qlen(LastQLen1,State4), + State6 = + ?update_max_time(?diff_time(T1,_T0), + State5#{last_qlen := LastQLen1, + last_load_ts => T1}), + State7 = case Result of + ok -> + S = ?update_freq(T1,State6), + ?update_other(writes,WRITES,1,S); + _ -> + State6 + end, + {Result,State7}. + + +%%%----------------------------------------------------------------- +%%% Check that the options are valid +check_opts(Options) when is_map(Options) -> + case do_check_opts(maps:to_list(Options)) of + ok -> + case overload_levels_ok(Options) of + true -> + ok; + false -> + Faulty = maps:with([sync_mode_qlen, + drop_mode_qlen, + flush_qlen],Options), + {error,{invalid_olp_levels,Faulty}} + end; + {error,Key,Value} -> + {error,{invalid_olp_config,#{Key=>Value}}} + end. + +do_check_opts([{sync_mode_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{drop_mode_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{flush_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{burst_limit_enable,Bool}|Options]) when is_boolean(Bool) -> + do_check_opts(Options); +do_check_opts([{burst_limit_max_count,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{burst_limit_window_time,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{overload_kill_enable,Bool}|Options]) when is_boolean(Bool) -> + do_check_opts(Options); +do_check_opts([{overload_kill_qlen,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{overload_kill_mem_size,N}|Options]) when is_integer(N) -> + do_check_opts(Options); +do_check_opts([{overload_kill_restart_after,NorA}|Options]) + when is_integer(NorA); NorA == infinity -> + do_check_opts(Options); +do_check_opts([{Key,Value}|_]) -> + {error,Key,Value}; +do_check_opts([]) -> + ok. + +set_restart_flag(#{id := Name, module := Module}) -> + Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), + spawn(fun() -> + register(Flag, self()), + timer:sleep(infinity) + end), + ok. + +reset_restart_flag(#{id := Name, module := Module} = State) -> + Flag = list_to_atom(lists:concat([Module,"_",Name,"_restarting"])), + case whereis(Flag) of + undefined -> + State; + Pid -> + exit(Pid, kill), + notify(restart,State) + end. + +check_load(State = #{id:=_Name, mode_ref := ModeRef, mode := Mode, + sync_mode_qlen := SyncModeQLen, + drop_mode_qlen := DropModeQLen, + flush_qlen := FlushQLen}) -> + {_,Mem} = process_info(self(), memory), + ?observe(_Name,{max_mem,Mem}), + {_,QLen} = process_info(self(), message_queue_len), + ?observe(_Name,{max_qlen,QLen}), + %% When the handler process gets scheduled in, it's impossible + %% to predict the QLen. We could jump "up" arbitrarily from say + %% async to sync, async to drop, sync to flush, etc. However, when + %% the handler process manages the log events (without flushing), + %% one after the other, we will move "down" from drop to sync and + %% from sync to async. This way we don't risk getting stuck in + %% drop or sync mode with an empty mailbox. + {Mode1,_NewDrops,_NewFlushes} = + if + QLen >= FlushQLen -> + {flush, 0,1}; + QLen >= DropModeQLen -> + %% Note that drop mode will force load messages to + %% be dropped on the client side (never sent to + %% the olp process). + IncDrops = if Mode == drop -> 0; true -> 1 end, + {?change_mode(ModeRef, Mode, drop), IncDrops,0}; + QLen >= SyncModeQLen -> + {?change_mode(ModeRef, Mode, sync), 0,0}; + true -> + {?change_mode(ModeRef, Mode, async), 0,0} + end, + State1 = ?update_other(drops,DROPS,_NewDrops,State), + State2 = ?update_max_qlen(QLen,State1), + State3 = maybe_notify_mode_change(Mode1,State2), + {Mode1, QLen, Mem, + ?update_other(flushes,FLUSHES,_NewFlushes, + State3#{last_qlen => QLen})}. + +limit_burst(#{burst_limit_enable := false}=State) -> + {true,State}; +limit_burst(#{burst_win_ts := BurstWinT0, + burst_msg_count := BurstMsgCount, + burst_limit_window_time := BurstLimitWinTime, + burst_limit_max_count := BurstLimitMaxCnt} = State) -> + if (BurstMsgCount >= BurstLimitMaxCnt) -> + %% the limit for allowed messages has been reached + BurstWinT1 = ?timestamp(), + case ?diff_time(BurstWinT1,BurstWinT0) of + BurstCheckTime when BurstCheckTime < (BurstLimitWinTime*1000) -> + %% we're still within the burst time frame + {false,?update_other(burst_drops,BURSTS,1,State)}; + _BurstCheckTime -> + %% burst time frame passed, reset counters + {true,State#{burst_win_ts => BurstWinT1, + burst_msg_count => 0}} + end; + true -> + %% the limit for allowed messages not yet reached + {true,State#{burst_win_ts => BurstWinT0, + burst_msg_count => BurstMsgCount+1}} + end. + +kill_if_choked(QLen, Mem, #{overload_kill_enable := KillIfOL, + overload_kill_qlen := OLKillQLen, + overload_kill_mem_size := OLKillMem}) -> + if KillIfOL andalso + ((QLen > OLKillQLen) orelse (Mem > OLKillMem)) -> + exit({shutdown,{overloaded,QLen,Mem}}); + true -> + ok + end. + +flush_load(Limit) -> + process_flag(priority, high), + Flushed = flush_load(0, Limit), + process_flag(priority, normal), + Flushed. + +flush_load(Limit, Limit) -> + Limit; +flush_load(N, Limit) -> + %% flush log events but leave other events, such as info, reset + %% and stop, so that these have a chance to be processed even + %% under heavy load + receive + {'$gen_cast',{'$olp_load',_}} -> + flush_load(N+1, Limit); + {'$gen_call',{Pid,MRef},{'$olp_load',_}} -> + Pid ! {MRef, dropped}, + flush_load(N+1, Limit); + {log,_,_,_,_} -> + flush_load(N+1, Limit); + {log,_,_,_} -> + flush_load(N+1, Limit) + after + 0 -> N + end. + +overload_levels_ok(Options) -> + SMQL = maps:get(sync_mode_qlen, Options, ?SYNC_MODE_QLEN), + DMQL = maps:get(drop_mode_qlen, Options, ?DROP_MODE_QLEN), + FQL = maps:get(flush_qlen, Options, ?FLUSH_QLEN), + (DMQL > 1) andalso (SMQL =< DMQL) andalso (DMQL =< FQL). + +maybe_notify_mode_change(drop,#{mode:=Mode0}=State) + when Mode0=/=drop -> + notify({mode_change,Mode0,drop},State); +maybe_notify_mode_change(Mode1,#{mode:=drop}=State) + when Mode1==async; Mode1==sync -> + notify({mode_change,drop,Mode1},State); +maybe_notify_mode_change(_,State) -> + State. + +notify(Note,#{module:=Module,cb_state:=CBState}=State) -> + CBState1 = try_callback_call(Module,notify,[Note,CBState],CBState), + State#{cb_state=>CBState1}. + +try_callback_call(Module, Function, Args) -> + try_callback_call(Module, Function, Args, '$no_default_return'). + +try_callback_call(Module, Function, Args, DefRet) -> + try apply(Module, Function, Args) + catch + throw:R -> R; + error:undef:S when DefRet=/='$no_default_return' -> + case S of + [{Module,Function,Args,_}|_] -> + DefRet; + _ -> + erlang:raise(error,undef,S) + end + end. + +noreply_return(#{idle:=true}=State) -> + {noreply,State}; +noreply_return(#{idle:=false}=State) -> + {noreply,State,?IDLE_DETECT_TIME}. + +reply_return(Reply,#{idle:=true}=State) -> + {reply,Reply,State}; +reply_return(Reply,#{idle:=false}=State) -> + {reply,Reply,State,?IDLE_DETECT_TIME}. diff --git a/lib/kernel/src/logger_olp.hrl b/lib/kernel/src/logger_olp.hrl new file mode 100644 index 0000000000..9b4f5ebf27 --- /dev/null +++ b/lib/kernel/src/logger_olp.hrl @@ -0,0 +1,180 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1997-2015. 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% +%% + +%%%----------------------------------------------------------------- +%%% Overload protection configuration + +%%! *** NOTE *** +%%! It's important that: +%%! SYNC_MODE_QLEN =< DROP_MODE_QLEN =< FLUSH_QLEN +%%! and that DROP_MODE_QLEN >= 2. +%%! Otherwise the process could end up in drop mode with no new +%%! log requests to process. This would cause all future requests +%%! to be dropped (no switch to async mode would ever take place). + +%% This specifies the message_queue_len value where the log +%% requests switch from asynchronous casts to synchronous calls. +-define(SYNC_MODE_QLEN, 10). +%% Above this message_queue_len, log requests will be dropped, +%% i.e. no log requests get sent to the process. +-define(DROP_MODE_QLEN, 200). +%% Above this message_queue_len, the process will flush its mailbox +%% and only leave this number of messages in it. +-define(FLUSH_QLEN, 1000). + +%% Never flush more than this number of messages in one go, or the +%% process will be unresponsive for seconds (keep this number as large +%% as possible or the mailbox could grow large). +-define(FLUSH_MAX_N, 5000). + +%% BURST_LIMIT_MAX_COUNT is the max number of log requests allowed +%% to be written within a BURST_LIMIT_WINDOW_TIME time frame. +-define(BURST_LIMIT_ENABLE, true). +-define(BURST_LIMIT_MAX_COUNT, 500). +-define(BURST_LIMIT_WINDOW_TIME, 1000). + +%% This enables/disables the feature to automatically terminate the +%% process if it gets too loaded (and can't keep up). +-define(OVERLOAD_KILL_ENABLE, false). +%% If the message_queue_len goes above this size even after +%% flushing has been performed, the process is terminated. +-define(OVERLOAD_KILL_QLEN, 20000). +%% If the memory usage exceeds this level, the process is terminated. +-define(OVERLOAD_KILL_MEM_SIZE, 3000000). + +%% This is the default time to wait before restarting and accepting +%% new requests. The value 'infinity' disables restarts. +-define(OVERLOAD_KILL_RESTART_AFTER, 5000). + +%% This is the time in milliseconds after last load message received +%% that we notify the callback about being idle. +-define(IDLE_DETECT_TIME, 100). + +%%%----------------------------------------------------------------- +%%% Overload protection macros + +-define(timestamp(), erlang:monotonic_time(microsecond)). + +-define(get_mode(Tid), + case ets:lookup(Tid, mode) of + [{mode,M}] -> M; + _ -> async + end). + +-define(set_mode(Tid, M), + begin ets:insert(Tid, {mode,M}), M end). + +-define(change_mode(Tid, M0, M1), + if M0 == M1 -> + M0; + true -> + ets:insert(Tid, {mode,M1}), + M1 + end). + +-define(max(X1, X2), + if + X2 == undefined -> X1; + X2 > X1 -> X2; + true -> X1 + end). + +-define(diff_time(OS_T1, OS_T0), OS_T1-OS_T0). + +%%%----------------------------------------------------------------- +%%% These macros enable statistics counters in the state of the +%%% process, which is useful for analysing the overload protection +%%% behaviour. These counters should not be included in code to be +%%% officially released (as some counters will grow very large over +%%% time). + +%% -define(SAVE_STATS, true). +-ifdef(SAVE_STATS). + -define(merge_with_stats(STATE), + begin + TIME = ?timestamp(), + STATE#{start => TIME, time => {TIME,0}, + flushes => 0, flushed => 0, drops => 0, + burst_drops => 0, casts => 0, calls => 0, + writes => 0, max_qlen => 0, max_time => 0, + freq => {TIME,0,0}} end). + + -define(update_max_qlen(QLEN, STATE), + begin #{max_qlen := QLEN0} = STATE, + STATE#{max_qlen => ?max(QLEN0,QLEN)} end). + + -define(update_calls_or_casts(CALL_OR_CAST, INC, STATE), + case CALL_OR_CAST of + cast -> + #{casts := CASTS0} = STATE, + STATE#{casts => CASTS0+INC}; + call -> + #{calls := CALLS0} = STATE, + STATE#{calls => CALLS0+INC} + end). + + -define(update_max_time(TIME, STATE), + begin #{max_time := TIME0} = STATE, + STATE#{max_time => ?max(TIME0,TIME)} end). + + -define(update_other(OTHER, VAR, INCVAL, STATE), + begin #{OTHER := VAR} = STATE, + STATE#{OTHER => VAR+INCVAL} end). + + -define(update_freq(TIME,STATE), + begin + case STATE of + #{freq := {START, 49, _}} -> + STATE#{freq => {TIME, 0, trunc(1000000*50/(?diff_time(TIME,START)))}}; + #{freq := {START, N, FREQ}} -> + STATE#{freq => {START, N+1, FREQ}} + end end). + + -define(update_time(TIME,STATE), + begin #{start := START} = STATE, + STATE#{time => {TIME,trunc((?diff_time(TIME,START))/1000000)}} end). + +-else. % DEFAULT! + -define(merge_with_stats(STATE), STATE). + -define(update_max_qlen(_QLEN, STATE), STATE). + -define(update_calls_or_casts(_CALL_OR_CAST, _INC, STATE), STATE). + -define(update_max_time(_TIME, STATE), STATE). + -define(update_other(_OTHER, _VAR, _INCVAL, STATE), STATE). + -define(update_freq(_TIME, STATE), STATE). + -define(update_time(_TIME, STATE), STATE). +-endif. + +%%%----------------------------------------------------------------- +%%% These macros enable callbacks that make it possible to analyse the +%%% overload protection behaviour from outside the process (including +%%% dropped requests on the client side). An external callback module +%%% (?OBSERVER_MOD) is required which is not part of the kernel +%%% application. For this reason, these callbacks should not be +%%% included in code to be officially released. + +%%-define(OBSERVER_MOD, logger_test). +-ifdef(OBSERVER_MOD). + -define(start_observation(NAME), ?OBSERVER:start_observation(NAME)). + -define(observe(NAME,EVENT), ?OBSERVER:observe(NAME,EVENT)). + +-else. % DEFAULT! + -define(start_observation(_NAME), ok). + -define(observe(_NAME,_EVENT), ok). +-endif. diff --git a/lib/kernel/src/logger_proxy.erl b/lib/kernel/src/logger_proxy.erl new file mode 100644 index 0000000000..24b293805c --- /dev/null +++ b/lib/kernel/src/logger_proxy.erl @@ -0,0 +1,165 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017-2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(logger_proxy). + +%% API +-export([start_link/0, restart/0, log/1, child_spec/0, get_default_config/0]). + +%% logger_olp callbacks +-export([init/1, handle_load/2, handle_info/2, terminate/2, + notify/2]). + +-include("logger_internal.hrl"). + +-define(SERVER,?MODULE). + +%%%----------------------------------------------------------------- +%%% API +-spec log(RemoteLog) -> ok when + RemoteLog :: {remote,node(),LogEvent}, + LogEvent :: {log,Level,Format,Args,Meta} | + {log,Level,StringOrReport,Meta}, + Level :: logger:level(), + Format :: io:format(), + Args :: list(term()), + StringOrReport :: unicode:chardata() | logger:report(), + Meta :: logger:metadata(). +log(RemoteLog) -> + Olp = persistent_term:get(?MODULE), + case logger_olp:get_pid(Olp) =:= self() of + true -> + %% This happens when the log event comes from the + %% emulator, and the group leader is on a remote node. + _ = handle_load(RemoteLog, no_state), + ok; + false -> + logger_olp:load(Olp, RemoteLog) + end. + +%% Called by supervisor +-spec start_link() -> {ok,pid(),logger_olp:olp_ref()} | {error,term()}. +start_link() -> + %% Notice that sync_mode is only used when logging to remote node, + %% i.e. when the log/2 API function is called. + %% + %% When receiving log events from the emulator or from a remote + %% node, the log event is sent as a message to this process, and + %% thus received directly in handle_info/2. This means that the + %% mode (async/sync/drop) is not read before the message is + %% sent. Thus sync mode is never entered, and drop mode is + %% implemented by setting the system_logger flag to undefined (see + %% notify/2) + %% + %% Burst limit is disabled, since this is only a proxy and we + %% don't want to limit bursts twice (here and in the handler). + logger_olp:start_link(?SERVER,?MODULE,[],logger:get_proxy_config()). + +%% Fun used for restarting this process after it has been killed due +%% to overload (must set overload_kill_enable=>true in opts) +restart() -> + case supervisor:start_child(logger_sup, child_spec()) of + {ok,_Pid,Olp} -> + {ok,Olp}; + {error,{Reason,Ch}} when is_tuple(Ch), element(1,Ch)==child -> + {error,Reason}; + Error -> + Error + end. + +%% Called internally and by logger_sup +child_spec() -> + Name = ?SERVER, + #{id => Name, + start => {?MODULE, start_link, []}, + restart => temporary, + shutdown => 2000, + type => worker, + modules => [?MODULE]}. + +get_default_config() -> + OlpDefault = logger_olp:get_default_opts(), + OlpDefault#{sync_mode_qlen=>500, + drop_mode_qlen=>1000, + flush_qlen=>5000, + burst_limit_enable=>false}. + +%%%=================================================================== +%%% gen_server callbacks +%%%=================================================================== + +init([]) -> + process_flag(trap_exit, true), + _ = erlang:system_flag(system_logger,self()), + persistent_term:put(?MODULE,logger_olp:get_ref()), + {ok,no_state}. + +%% Log event to send to the node where the group leader of it's client resides +handle_load({remote,Node,Log},State) -> + %% If the connection is overloaded (send_nosuspend returns false), + %% we drop the message. + _ = erlang:send_nosuspend({?SERVER,Node},Log), + State; +%% Log event to log on this node +handle_load({log,Level,Format,Args,Meta},State) -> + try_log([Level,Format,Args,Meta]), + State; +handle_load({log,Level,Report,Meta},State) -> + try_log([Level,Report,Meta]), + State. + +%% Log event sent to this process e.g. from the emulator - it is really load +handle_info(Log,State) when is_tuple(Log), element(1,Log)==log -> + {load,State}. + +terminate(overloaded, _State) -> + _ = erlang:system_flag(system_logger,undefined), + {ok,fun ?MODULE:restart/0}; +terminate(_Reason, _State) -> + _ = erlang:system_flag(system_logger,whereis(logger)), + ok. + +notify({mode_change,Mode0,Mode1},State) -> + _ = if Mode1=:=drop -> % entering drop mode + erlang:system_flag(system_logger,undefined); + Mode0=:=drop -> % leaving drop mode + erlang:system_flag(system_logger,self()); + true -> + ok + end, + ?LOG_INTERNAL(notice,"~w switched from ~w to ~w mode",[?MODULE,Mode0,Mode1]), + State; +notify({flushed,Flushed},State) -> + ?LOG_INTERNAL(notice, "~w flushed ~w log events",[?MODULE,Flushed]), + State; +notify(restart,State) -> + ?LOG_INTERNAL(notice, "~w restarted", [?MODULE]), + State; +notify(_Note,State) -> + State. + +%%%----------------------------------------------------------------- +%%% Internal functions +try_log(Args) -> + try apply(logger,log,Args) + catch C:R:S -> + ?LOG_INTERNAL(debug,[{?MODULE,log_failed}, + {log,Args}, + {reason,{C,R,S}}]) + end. diff --git a/lib/kernel/src/logger_server.erl b/lib/kernel/src/logger_server.erl index b7735dbcf7..722246e82c 100644 --- a/lib/kernel/src/logger_server.erl +++ b/lib/kernel/src/logger_server.erl @@ -22,8 +22,7 @@ -behaviour(gen_server). %% API --export([start_link/0, - add_handler/3, remove_handler/1, +-export([start_link/0, add_handler/3, remove_handler/1, add_filter/2, remove_filter/2, set_module_level/2, unset_module_level/0, unset_module_level/1, cache_module_level/1, @@ -43,7 +42,7 @@ -define(SERVER, logger). -define(LOGGER_SERVER_TAG, '$logger_cb_process'). --record(state, {tid, async_req, async_req_queue}). +-record(state, {tid, async_req, async_req_queue, remote_logger}). %%%=================================================================== %%% API @@ -155,6 +154,8 @@ init([]) -> process_flag(trap_exit, true), put(?LOGGER_SERVER_TAG,true), Tid = logger_config:new(?LOGGER_TABLE), + %% Store initial proxy config. logger_proxy reads config from here at startup. + logger_config:create(Tid,proxy,logger_proxy:get_default_config()), PrimaryConfig = maps:merge(default_config(primary), #{handlers=>[simple]}), logger_config:create(Tid,primary,PrimaryConfig), @@ -221,6 +222,24 @@ handle_call({add_filter,Id,Filter}, _From,#state{tid=Tid}=State) -> handle_call({remove_filter,Id,FilterId}, _From, #state{tid=Tid}=State) -> Reply = do_remove_filter(Tid,Id,FilterId), {reply,Reply,State}; +handle_call({change_config,SetOrUpd,proxy,Config0},_From,#state{tid=Tid}=State) -> + Default = + case SetOrUpd of + set -> + logger_proxy:get_default_config(); + update -> + {ok,OldConfig} = logger_config:get(Tid,proxy), + OldConfig + end, + Config = maps:merge(Default,Config0), + Reply = + case logger_olp:set_opts(logger_proxy,Config) of + ok -> + logger_config:set(Tid,proxy,Config); + Error -> + Error + end, + {reply,Reply,State}; handle_call({change_config,SetOrUpd,primary,Config0}, _From, #state{tid=Tid}=State) -> {ok,#{handlers:=Handlers}=OldConfig} = logger_config:get(Tid,primary), @@ -357,7 +376,7 @@ terminate(_Reason, _State) -> %%%=================================================================== %%% Internal functions %%%=================================================================== -call(Request) -> +call(Request) when is_tuple(Request) -> Action = element(1,Request), case get(?LOGGER_SERVER_TAG) of true when @@ -369,6 +388,7 @@ call(Request) -> gen_server:call(?SERVER,Request,?DEFAULT_LOGGER_CALL_TIMEOUT) end. + do_add_filter(Tid,Id,{FId,_} = Filter) -> case logger_config:get(Tid,Id) of {ok,Config} -> @@ -413,11 +433,13 @@ default_config(Id,Module) -> sanity_check(Owner,Key,Value) -> sanity_check_1(Owner,[{Key,Value}]). -sanity_check(HandlerId,Config) when is_map(Config) -> - sanity_check_1(HandlerId,maps:to_list(Config)); +sanity_check(Owner,Config) when is_map(Config) -> + sanity_check_1(Owner,maps:to_list(Config)); sanity_check(_,Config) -> {error,{invalid_config,Config}}. +sanity_check_1(proxy,_Config) -> + ok; % Details are checked by logger_olp:set_opts/2 sanity_check_1(Owner,Config) when is_list(Config) -> try Type = get_type(Owner), diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl index 8b51dd8569..fe181722f3 100644 --- a/lib/kernel/src/logger_simple_h.erl +++ b/lib/kernel/src/logger_simple_h.erl @@ -50,7 +50,6 @@ removing_handler(#{id:=simple}) -> ok; Pid -> Ref = erlang:monitor(process,Pid), - unlink(Pid), Pid ! stop, receive {'DOWN',Ref,process,Pid,_} -> ok @@ -99,7 +98,11 @@ loop(Buffer) -> replay_buffer(Buffer); _ -> ok - end; + end, + %% Before stopping, we unlink the logger process to avoid + %% an unexpected EXIT message + unlink(whereis(logger)), + ok; {log,#{msg:=_,meta:=#{time:=_}}=Log} -> do_log(Log), loop(update_buffer(Buffer,Log)); diff --git a/lib/kernel/src/logger_std_h.erl b/lib/kernel/src/logger_std_h.erl index 42e0f5caf4..0669164bb6 100644 --- a/lib/kernel/src/logger_std_h.erl +++ b/lib/kernel/src/logger_std_h.erl @@ -19,8 +19,6 @@ %% -module(logger_std_h). --behaviour(gen_server). - -include("logger.hrl"). -include("logger_internal.hrl"). -include("logger_h_common.hrl"). @@ -28,117 +26,97 @@ -include_lib("kernel/include/file.hrl"). %% API --export([start_link/3, info/1, filesync/1, reset/1]). +-export([filesync/1]). -%% gen_server and proc_lib callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). +%% logger_h_common callbacks +-export([init/2, check_config/4, reset_state/2, + filesync/3, write/4, handle_info/3, terminate/3]). %% logger callbacks -export([log/2, adding_handler/1, removing_handler/1, changing_config/3, filter_config/1]). -%% handler internal --export([log_handler_info/4]). +-define(DEFAULT_CALL_TIMEOUT, 5000). %%%=================================================================== %%% API %%%=================================================================== %%%----------------------------------------------------------------- -%%% Start a standard handler process and link to caller. -%%% This function is called by the kernel supervisor when this -%%% handler process gets added --spec start_link(Name, Config, HandlerState) -> {ok,Pid} | {error,Reason} when +%%% +-spec filesync(Name) -> ok | {error,Reason} when Name :: atom(), + Reason :: handler_busy | {badarg,term()}. + +filesync(Name) -> + logger_h_common:filesync(?MODULE,Name). + +%%%=================================================================== +%%% logger callbacks - just forward to logger_h_common +%%%=================================================================== + +%%%----------------------------------------------------------------- +%%% Handler being added +-spec adding_handler(Config) -> {ok,Config} | {error,Reason} when Config :: logger:handler_config(), - HandlerState :: map(), - Pid :: pid(), Reason :: term(). -start_link(Name, Config, HandlerState) -> - proc_lib:start_link(?MODULE,init,[[Name,Config,HandlerState]]). +adding_handler(Config) -> + logger_h_common:adding_handler(Config). %%%----------------------------------------------------------------- -%%% --spec filesync(Name) -> ok | {error,Reason} when - Name :: atom(), - Reason :: handler_busy | {badarg,term()}. +%%% Updating handler config +-spec changing_config(SetOrUpdate, OldConfig, NewConfig) -> + {ok,Config} | {error,Reason} when + SetOrUpdate :: set | update, + OldConfig :: logger:handler_config(), + NewConfig :: logger:handler_config(), + Config :: logger:handler_config(), + Reason :: term(). -filesync(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - filesync, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -filesync(Name) -> - {error,{badarg,{filesync,[Name]}}}. +changing_config(SetOrUpdate, OldConfig, NewConfig) -> + logger_h_common:changing_config(SetOrUpdate, OldConfig, NewConfig). %%%----------------------------------------------------------------- -%%% --spec info(Name) -> Info | {error,Reason} when - Name :: atom(), - Info :: term(), - Reason :: handler_busy | {badarg,term()}. +%%% Handler being removed +-spec removing_handler(Config) -> ok when + Config :: logger:handler_config(). -info(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - info, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -info(Name) -> - {error,{badarg,{info,[Name]}}}. +removing_handler(Config) -> + logger_h_common:removing_handler(Config). %%%----------------------------------------------------------------- -%%% --spec reset(Name) -> ok | {error,Reason} when - Name :: atom(), - Reason :: handler_busy | {badarg,term()}. +%%% Log a string or report +-spec log(LogEvent, Config) -> ok when + LogEvent :: logger:log_event(), + Config :: logger:handler_config(). -reset(Name) when is_atom(Name) -> - try - gen_server:call(?name_to_reg_name(?MODULE,Name), - reset, ?DEFAULT_CALL_TIMEOUT) - catch - _:{timeout,_} -> {error,handler_busy} - end; -reset(Name) -> - {error,{badarg,{reset,[Name]}}}. +log(LogEvent, Config) -> + logger_h_common:log(LogEvent, Config). + +%%%----------------------------------------------------------------- +%%% Remove internal fields from configuration +-spec filter_config(Config) -> Config when + Config :: logger:handler_config(). +filter_config(Config) -> + logger_h_common:filter_config(Config). %%%=================================================================== -%%% logger callbacks +%%% logger_h_common callbacks %%%=================================================================== - -%%%----------------------------------------------------------------- -%%% Handler being added -adding_handler(#{id:=Name}=Config) -> - case check_config(adding, Config) of - {ok, #{config:=HConfig}=Config1} -> - %% create initial handler state by merging defaults with config - HState = maps:merge(get_init_state(), HConfig), - case logger_h_common:overload_levels_ok(HState) of - true -> - start(Name, Config1, HState); - false -> - #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = HState, - {error,{invalid_levels,{SMQL,DMQL,FQL}}} - end; +init(Name, #{type := Type}) -> + case open_log_file(Name, Type) of + {ok,FileCtrlPid} -> + {ok,#{type=>Type,file_ctrl_pid=>FileCtrlPid}}; Error -> Error end. -%%%----------------------------------------------------------------- -%%% Updating handler config -changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) -> +check_config(_Name,set,undefined,NewHConfig) -> + check_config(maps:merge(get_default_config(),NewHConfig)); +check_config(_Name,SetOrUpdate,OldHConfig,NewHConfig0) -> WriteOnce = maps:with([type],OldHConfig), - ReadOnly = maps:with([handler_pid,mode_tab],OldHConfig), - NewHConfig0 = maps:get(config, NewConfig, #{}), Default = case SetOrUpdate of set -> @@ -148,48 +126,24 @@ changing_config(SetOrUpdate,OldConfig=#{config:=OldHConfig},NewConfig) -> OldHConfig end, - %% Allow (accidentially) included read-only fields - just overwrite them - NewHConfig = maps:merge(maps:merge(Default, NewHConfig0),ReadOnly), + NewHConfig = maps:merge(Default, NewHConfig0), - %% But fail if write-once fields are changed + %% Fail if write-once fields are changed case maps:with([type],NewHConfig) of WriteOnce -> - changing_config1(maps:get(handler_pid,OldHConfig), - OldConfig, - NewConfig#{config=>NewHConfig}); + check_config(NewHConfig); Other -> - {error,{illegal_config_change,#{config=>WriteOnce},#{config=>Other}}} + {error,{illegal_config_change,?MODULE,WriteOnce,Other}} end. -changing_config1(HPid, OldConfig, NewConfig) -> - case check_config(changing, NewConfig) of - Result = {ok,NewConfig1} -> - try gen_server:call(HPid, {change_config,OldConfig,NewConfig1}, - ?DEFAULT_CALL_TIMEOUT) of - ok -> Result; - HError -> HError - catch - _:{timeout,_} -> {error,handler_busy} - end; - Error -> - Error - end. - -check_config(adding, Config) -> - %% Merge in defaults on handler level - HConfig0 = maps:get(config, Config, #{}), - HConfig = maps:merge(get_default_config(),HConfig0), +check_config(#{type:=Type}=HConfig) -> case check_h_config(maps:to_list(HConfig)) of + ok when is_atom(Type) -> + {ok,HConfig#{filesync_repeat_interval=>no_repeat}}; ok -> - {ok,Config#{config=>HConfig}}; - Error -> - Error - end; -check_config(changing, Config) -> - HConfig = maps:get(config, Config, #{}), - case check_h_config(maps:to_list(HConfig)) of - ok -> {ok,Config}; - Error -> Error + {ok,HConfig}; + {error,{Key,Value}} -> + {error,{invalid_config,?MODULE,#{Key=>Value}}} end. check_h_config([{type,Type} | Config]) when Type == standard_io; @@ -200,219 +154,40 @@ check_h_config([{type,{file,File}} | Config]) when is_list(File) -> check_h_config([{type,{file,File,Modes}} | Config]) when is_list(File), is_list(Modes) -> check_h_config(Config); -check_h_config([Other | Config]) -> - case logger_h_common:check_common_config(Other) of - valid -> - check_h_config(Config); - invalid -> - {error,{invalid_config,?MODULE,Other}} - end; +check_h_config([Other | _]) -> + {error,Other}; check_h_config([]) -> ok. - -%%%----------------------------------------------------------------- -%%% Handler being removed -removing_handler(#{id:=Name}) -> - stop(Name). - -%%%----------------------------------------------------------------- -%%% Log a string or report --spec log(LogEvent, Config) -> ok when - LogEvent :: logger:log_event(), - Config :: logger:handler_config(). - -log(LogEvent, Config = #{id := Name, - config := #{handler_pid := HPid, - mode_tab := ModeTab}}) -> - %% if the handler has crashed, we must drop this event - %% and hope the handler restarts so we can try again - true = is_process_alive(HPid), - Bin = logger_h_common:log_to_binary(LogEvent, Config), - logger_h_common:call_cast_or_drop(Name, HPid, ModeTab, Bin). - -%%%----------------------------------------------------------------- -%%% Remove internal fields from configuration -filter_config(#{config:=HConfig}=Config) -> - Config#{config=>maps:without([handler_pid,mode_tab],HConfig)}. - -%%%=================================================================== -%%% gen_server callbacks -%%%=================================================================== - -init([Name, Config = #{config := HConfig}, - State0 = #{type := Type, file_ctrl_sync_int := FileCtrlSyncInt}]) -> - RegName = ?name_to_reg_name(?MODULE,Name), - register(RegName, self()), - process_flag(trap_exit, true), - process_flag(message_queue_data, off_heap), - - ?init_test_hooks(), - ?start_observation(Name), - - case do_init(Name, Type) of - {ok,InitState} -> - try ets:new(Name, [public]) of - ModeTab -> - ?set_mode(ModeTab, async), - State = maps:merge(State0, InitState), - T0 = ?timestamp(), - State1 = - ?merge_with_stats(State#{ - mode_tab => ModeTab, - mode => async, - file_ctrl_sync => FileCtrlSyncInt, - last_qlen => 0, - last_log_ts => T0, - last_op => sync, - burst_win_ts => T0, - burst_msg_count => 0}), - Config1 = - Config#{config => HConfig#{handler_pid => self(), - mode_tab => ModeTab}}, - proc_lib:init_ack({ok,self(),Config1}), - gen_server:cast(self(), repeated_filesync), - gen_server:enter_loop(?MODULE, [], State1) - catch - _:Error -> - unregister(RegName), - logger_h_common:error_notify({init_handler,Name,Error}), - proc_lib:init_ack(Error) - end; - Error -> - unregister(RegName), - logger_h_common:error_notify({init_handler,Name,Error}), - proc_lib:init_ack(Error) - end. - -do_init(Name, Type) -> - case open_log_file(Name, Type) of - {ok,FileCtrlPid} -> - case logger_h_common:unset_restart_flag(Name, ?MODULE) of - true -> - %% inform about restart - gen_server:cast(self(), {log_handler_info, - "Handler ~p restarted", - [Name]}); - false -> - %% initial start - ok - end, - {ok,#{id=>Name,type=>Type,file_ctrl_pid=>FileCtrlPid}}; - Error -> - Error - end. - -%% This is the synchronous log event. -handle_call({log, Bin}, _From, State) -> - {Result,State1} = do_log(Bin, call, State), - %% Result == ok | dropped - {reply,Result, State1}; - -handle_call(filesync, _From, State = #{type := Type, - file_ctrl_pid := FileCtrlPid}) -> - if is_atom(Type) -> - {reply, ok, State}; - true -> - {reply, file_ctrl_filesync_sync(FileCtrlPid), State#{last_op=>sync}} - end; - -handle_call({change_config,_OldConfig,NewConfig}, _From, - State = #{filesync_repeat_interval := FSyncInt0}) -> - HConfig = maps:get(config, NewConfig, #{}), - State1 = maps:merge(State, HConfig), - case logger_h_common:overload_levels_ok(State1) of - true -> - _ = - case maps:get(filesync_repeat_interval, HConfig, undefined) of - undefined -> - ok; - no_repeat -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)); - FSyncInt0 -> - ok; - _FSyncInt1 -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, - State, - undefined)), - gen_server:cast(self(), repeated_filesync) - end, - {reply, ok, State1}; - false -> - #{sync_mode_qlen := SMQL, - drop_mode_qlen := DMQL, - flush_qlen := FQL} = State1, - {reply, {error,{invalid_levels,{SMQL,DMQL,FQL}}}, State} - end; - -handle_call(info, _From, State) -> - {reply, State, State}; - -handle_call(reset, _From, State) -> - State1 = ?merge_with_stats(State), - {reply, ok, State1#{last_qlen => 0, - last_log_ts => ?timestamp()}}; - -handle_call(stop, _From, State) -> - {stop, {shutdown,stopped}, ok, State}. - -%% This is the asynchronous log event. -handle_cast({log, Bin}, State) -> - {_,State1} = do_log(Bin, cast, State), - {noreply, State1}; - -handle_cast({log_handler_info, Format, Args}, State = #{id:=Name}) -> - log_handler_info(Name, Format, Args, State), - {noreply, State}; - -%% If FILESYNC_REPEAT_INTERVAL is set to a millisec value, this -%% clause gets called repeatedly by the handler. In order to -%% guarantee that a filesync *always* happens after the last log -%% event, the repeat operation must be active! -handle_cast(repeated_filesync, - State = #{type := Type, - file_ctrl_pid := FileCtrlPid, - filesync_repeat_interval := FSyncInt, - last_op := LastOp}) -> - State1 = - if not is_atom(Type), is_integer(FSyncInt) -> - %% only do filesync if something has been - %% written since last time we checked - if LastOp == sync -> - ok; - true -> - file_ctrl_filesync_async(FileCtrlPid) - end, - {ok,TRef} = - timer:apply_after(FSyncInt, gen_server,cast, - [self(),repeated_filesync]), - State#{rep_sync_tref => TRef, last_op => sync}; - true -> - State - end, - {noreply,State1}. - -handle_info({'EXIT',Pid,Why}, State = #{id := Name, type := FileInfo}) -> - case maps:get(file_ctrl_pid, State, undefined) of - Pid -> - %% file error, terminate handler - logger_h_common:handler_exit(Name, - {error,{write_failed,FileInfo,Why}}); - _Other -> - %% ignore EXIT - ok - end, - {noreply, State}; - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(Reason, State = #{id:=Name, file_ctrl_pid:=FWPid, - type:=_FileInfo}) -> - _ = logger_h_common:cancel_timer(maps:get(rep_sync_tref, State, - undefined)), +get_default_config() -> + #{type => standard_io}. + +filesync(_Name, _Mode, #{type := Type}=State) when is_atom(Type) -> + {ok,State}; +filesync(_Name, async, #{file_ctrl_pid := FileCtrlPid} = State) -> + ok = file_ctrl_filesync_async(FileCtrlPid), + {ok,State}; +filesync(_Name, sync, #{file_ctrl_pid := FileCtrlPid} = State) -> + Result = file_ctrl_filesync_sync(FileCtrlPid), + {Result,State}. + +write(_Name, async, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) -> + ok = file_write_async(FileCtrlPid, Bin), + {ok,State}; +write(_Name, sync, Bin, #{file_ctrl_pid:=FileCtrlPid} = State) -> + Result = file_write_sync(FileCtrlPid, Bin), + {Result,State}. + +reset_state(_Name, State) -> + State. + +handle_info(_Name, {'EXIT',Pid,Why}, #{type := FileInfo, file_ctrl_pid := Pid}) -> + %% file_ctrl_pid died, file error, terminate handler + exit({error,{write_failed,FileInfo,Why}}); +handle_info(_, _, State) -> + State. + +terminate(_Name, _Reason, #{file_ctrl_pid:=FWPid}) -> case is_process_alive(FWPid) of true -> unlink(FWPid), @@ -423,17 +198,12 @@ terminate(Reason, State = #{id:=Name, file_ctrl_pid:=FWPid, ok after ?DEFAULT_CALL_TIMEOUT -> - exit(FWPid, kill) + exit(FWPid, kill), + ok end; false -> ok - end, - ok = logger_h_common:stop_or_restart(Name, Reason, State), - unregister(?name_to_reg_name(?MODULE, Name)), - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. + end. %%%=================================================================== %%% Internal functions @@ -441,203 +211,6 @@ code_change(_OldVsn, State, _Extra) -> %%%----------------------------------------------------------------- %%% -get_default_config() -> - #{type => standard_io, - sync_mode_qlen => ?SYNC_MODE_QLEN, - drop_mode_qlen => ?DROP_MODE_QLEN, - flush_qlen => ?FLUSH_QLEN, - burst_limit_enable => ?BURST_LIMIT_ENABLE, - burst_limit_max_count => ?BURST_LIMIT_MAX_COUNT, - burst_limit_window_time => ?BURST_LIMIT_WINDOW_TIME, - overload_kill_enable => ?OVERLOAD_KILL_ENABLE, - overload_kill_qlen => ?OVERLOAD_KILL_QLEN, - overload_kill_mem_size => ?OVERLOAD_KILL_MEM_SIZE, - overload_kill_restart_after => ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval => ?FILESYNC_REPEAT_INTERVAL}. - -get_init_state() -> - #{file_ctrl_sync_int => ?CONTROLLER_SYNC_INTERVAL, - filesync_ok_qlen => ?FILESYNC_OK_QLEN}. - -%%%----------------------------------------------------------------- -%%% Add a standard handler to the logger. -%%% This starts a dedicated handler process which should always -%%% exist if the handler is registered with logger (and should not -%%% exist if the handler is not registered). -%%% -%%% Handler specific config should be provided with a sub map associated -%%% with a key named 'config', e.g: -%%% -%%% Config = #{config => #{sync_mode_qlen => 50} -%%% -%%% The standard handler process is linked to logger_sup, which is -%%% part of the kernel application's supervision tree. -start(Name, Config, HandlerState) -> - LoggerStdH = - #{id => Name, - start => {?MODULE, start_link, [Name,Config,HandlerState]}, - restart => temporary, - shutdown => 2000, - type => worker, - modules => [?MODULE]}, - case supervisor:start_child(logger_sup, LoggerStdH) of - {ok,Pid,Config1} -> - ok = logger_handler_watcher:register_handler(Name,Pid), - {ok,Config1}; - Error -> - Error - end. - -%%%----------------------------------------------------------------- -%%% Stop and remove the handler. -stop(Name) -> - case whereis(?name_to_reg_name(?MODULE,Name)) of - undefined -> - ok; - Pid -> - %% We don't want to do supervisor:terminate_child here - %% since we need to distinguish this explicit stop from a - %% system termination in order to avoid circular attempts - %% at removing the handler (implying deadlocks and - %% timeouts). - %% And we don't need to do supervisor:delete_child, since - %% the restart type is temporary, which means that the - %% child specification is automatically removed from the - %% supervisor when the process dies. - _ = gen_server:call(Pid, stop), - ok - end. - -%%%----------------------------------------------------------------- -%%% Logging and overload control. --define(update_file_ctrl_sync(C, Interval), - if C == 0 -> Interval; - true -> C-1 end). - -%% check for overload between every event (and set Mode to async, -%% sync or drop accordingly), but never flush the whole mailbox -%% before LogWindowSize events have been handled -do_log(Bin, CallOrCast, State = #{id:=Name, mode:=Mode0}) -> - T1 = ?timestamp(), - - %% check if the handler is getting overloaded, or if it's - %% recovering from overload (the check must be done for each - %% event to react quickly to large bursts of events and - %% to ensure that the handler can never end up in drop mode - %% with an empty mailbox, which would stop operation) - {Mode1,QLen,Mem,State1} = logger_h_common:check_load(State), - - if (Mode1 == drop) andalso (Mode0 =/= drop) -> - log_handler_info(Name, "Handler ~p switched to drop mode", - [Name], State); - (Mode0 == drop) andalso ((Mode1 == async) orelse (Mode1 == sync)) -> - log_handler_info(Name, "Handler ~p switched to ~w mode", - [Name,Mode1], State); - true -> - ok - end, - - %% kill the handler if it can't keep up with the load - logger_h_common:kill_if_choked(Name, QLen, Mem, ?MODULE, State), - - if Mode1 == flush -> - flush(Name, QLen, T1, State1); - true -> - write(Name, Mode1, T1, Bin, CallOrCast, State1) - end. - -%% this clause is called by do_log/3 after an overload check -%% has been performed, where QLen > FlushQLen -flush(Name, _QLen0, T1, State=#{last_log_ts := _T0, mode_tab := ModeTab}) -> - %% flush messages in the mailbox (a limited number in - %% order to not cause long delays) - NewFlushed = logger_h_common:flush_log_events(?FLUSH_MAX_N), - - %% write info in log about flushed messages - log_handler_info(Name, "Handler ~p flushed ~w log events", - [Name,NewFlushed], State), - - %% because of the receive loop when flushing messages, the - %% handler will be scheduled out often and the mailbox could - %% grow very large, so we'd better check the queue again here - {_,_QLen1} = process_info(self(), message_queue_len), - ?observe(Name,{max_qlen,_QLen1}), - - %% Add 1 for the current log event - ?observe(Name,{flushed,NewFlushed+1}), - - State1 = ?update_max_time(?diff_time(T1,_T0),State), - {dropped,?update_other(flushed,FLUSHED,NewFlushed, - State1#{mode => ?set_mode(ModeTab,async), - last_qlen => 0, - last_log_ts => T1})}. - -%% this clause is called to write to file -write(_Name, Mode, T1, Bin, _CallOrCast, - State = #{mode_tab := ModeTab, - file_ctrl_pid := FileCtrlPid, - file_ctrl_sync := FileCtrlSync, - last_qlen := LastQLen, - last_log_ts := T0, - file_ctrl_sync_int := FileCtrlSyncInt}) -> - %% check if we need to limit the number of writes - %% during a burst of log events - {DoWrite,BurstWinT,BurstMsgCount} = logger_h_common:limit_burst(State), - - %% only send a synhrounous event to the file controller process - %% every FileCtrlSyncInt time, to give the handler time between - %% file writes so it can keep up with incoming messages - {Result,LastQLen1} = - if DoWrite, FileCtrlSync == 0 -> - ?observe(_Name,{_CallOrCast,1}), - file_write_sync(FileCtrlPid, Bin, false), - {ok,element(2, process_info(self(), message_queue_len))}; - DoWrite -> - ?observe(_Name,{_CallOrCast,1}), - file_write_async(FileCtrlPid, Bin), - {ok,LastQLen}; - not DoWrite -> - ?observe(_Name,{flushed,1}), - {dropped,LastQLen} - end, - - %% Check if the time since the previous log event is long enough - - %% and the queue length small enough - to assume the mailbox has - %% been emptied, and if so, do filesync operation and reset mode to - %% async. Note that this is the best we can do to detect an idle - %% handler without setting a timer after each log call/cast. If the - %% time between two consecutive log events is fast and no new - %% event comes in after the last one, idle state won't be detected! - Time = ?diff_time(T1,T0), - {Mode1,BurstMsgCount1} = - if (LastQLen1 < ?FILESYNC_OK_QLEN) andalso - (Time > ?IDLE_DETECT_TIME_USEC) -> - %% do filesync if necessary - case maps:get(type, State) of - Std when is_atom(Std) -> - ok; - _File -> - file_ctrl_filesync_async(FileCtrlPid) - end, - {?change_mode(ModeTab, Mode, async),0}; - true -> - {Mode,BurstMsgCount} - end, - State1 = - ?update_calls_or_casts(_CallOrCast,1,State), - State2 = - ?update_max_time(Time, - State1#{mode => Mode1, - last_qlen := LastQLen1, - last_log_ts => T1, - last_op => write, - burst_win_ts => BurstWinT, - burst_msg_count => BurstMsgCount1, - file_ctrl_sync => - ?update_file_ctrl_sync(FileCtrlSync, - FileCtrlSyncInt)}), - {Result,State2}. - open_log_file(HandlerName, FileInfo) -> case file_ctrl_start(HandlerName, FileInfo) of OK = {ok,_FileCtrlPid} -> OK; @@ -669,19 +242,6 @@ close_log_file(Fd) -> _ = file:close(Fd). -log_handler_info(Name, Format, Args, #{file_ctrl_pid := FileCtrlPid}) -> - Config = - case logger:get_handler_config(Name) of - {ok,Conf} -> Conf; - _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} - end, - Meta = #{time=>erlang:system_time(microsecond)}, - Bin = logger_h_common:log_to_binary(#{level => notice, - msg => {Format,Args}, - meta => Meta}, Config), - _ = file_write_async(FileCtrlPid, Bin), - ok. - %%%----------------------------------------------------------------- %%% File control process @@ -708,24 +268,19 @@ file_write_async(Pid, Bin) -> Pid ! {log,Bin}, ok. -file_write_sync(Pid, Bin, FileSync) -> - case file_ctrl_call(Pid, {log,self(),Bin,FileSync}) of - {error,Reason} -> - {error,{write_failed,Bin,Reason}}; - Result -> - Result - end. +file_write_sync(Pid, Bin) -> + file_ctrl_call(Pid, {log,Bin}). file_ctrl_filesync_async(Pid) -> Pid ! filesync, ok. file_ctrl_filesync_sync(Pid) -> - file_ctrl_call(Pid, {filesync,self()}). + file_ctrl_call(Pid, filesync). file_ctrl_call(Pid, Msg) -> MRef = monitor(process, Pid), - Pid ! {Msg,MRef}, + Pid ! {Msg,{self(),MRef}}, receive {MRef,Result} -> demonitor(MRef, [flush]), @@ -743,74 +298,43 @@ file_ctrl_init(HandlerName, FileInfo, Starter) when is_tuple(FileInfo) -> case do_open_log_file(FileInfo) of {ok,Fd} -> Starter ! {self(),ok}, - file_ctrl_loop(Fd, file, FileName, false, ok, ok, HandlerName); + file_ctrl_loop(Fd, FileName, false, ok, ok, HandlerName); {error,Reason} -> Starter ! {self(),{error,{open_failed,FileName,Reason}}} end; file_ctrl_init(HandlerName, StdDev, Starter) -> Starter ! {self(),ok}, - file_ctrl_loop(StdDev, standard_io, StdDev, false, ok, ok, HandlerName). + file_ctrl_loop(StdDev, StdDev, false, ok, ok, HandlerName). -file_ctrl_loop(Fd, Type, DevName, Synced, +file_ctrl_loop(Fd, DevName, Synced, PrevWriteResult, PrevSyncResult, HandlerName) -> receive %% asynchronous event {log,Bin} -> - Result = if Type == file -> - write_to_dev(Fd, Bin, DevName, - PrevWriteResult, HandlerName); - true -> - io:put_chars(Fd, Bin) - end, - file_ctrl_loop(Fd, Type, DevName, false, + Fd1 = ensure(Fd, DevName), + Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName), + file_ctrl_loop(Fd1, DevName, false, Result, PrevSyncResult, HandlerName); %% synchronous event - {{log,From,Bin,FileSync},MRef} -> - if Type == file -> - %% check that file hasn't been deleted - CheckFile = - fun() -> {ok,_} = file:read_file_info(DevName) end, - spawn_link(CheckFile), - WResult = write_to_dev(Fd, Bin, DevName, - PrevWriteResult, HandlerName), - {Synced1,SResult} = - if not FileSync -> - {false,PrevSyncResult}; - true -> - case sync_dev(Fd, DevName, - PrevSyncResult, HandlerName) of - ok -> {true,ok}; - Error -> {false,Error} - end - end, - From ! {MRef,ok}, - file_ctrl_loop(Fd, Type, DevName, Synced1, - WResult, SResult, HandlerName); - true -> - _ = io:put_chars(Fd, Bin), - From ! {MRef,ok}, - file_ctrl_loop(Fd, Type, DevName, false, - ok, PrevSyncResult, HandlerName) - end; + {{log,Bin},{From,MRef}} -> + Fd1 = ensure(Fd, DevName), + Result = write_to_dev(Fd1, Bin, DevName, PrevWriteResult, HandlerName), + From ! {MRef,ok}, + file_ctrl_loop(Fd1, DevName, false, + Result, PrevSyncResult, HandlerName); - filesync when not Synced -> - Result = sync_dev(Fd, DevName, PrevSyncResult, HandlerName), - file_ctrl_loop(Fd, Type, DevName, true, + filesync -> + Fd1 = ensure(Fd, DevName), + Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName), + file_ctrl_loop(Fd1, DevName, true, PrevWriteResult, Result, HandlerName); - filesync -> - file_ctrl_loop(Fd, Type, DevName, true, - PrevWriteResult, PrevSyncResult, HandlerName); - - {{filesync,From},MRef} -> - Result = if not Synced -> - sync_dev(Fd, DevName, PrevSyncResult, HandlerName); - true -> - ok - end, + {filesync,{From,MRef}} -> + Fd1 = ensure(Fd, DevName), + Result = sync_dev(Fd1, DevName, Synced, PrevSyncResult, HandlerName), From ! {MRef,ok}, - file_ctrl_loop(Fd, Type, DevName, true, + file_ctrl_loop(Fd1, DevName, true, PrevWriteResult, Result, HandlerName); stop -> @@ -818,26 +342,44 @@ file_ctrl_loop(Fd, Type, DevName, Synced, stopped end. -write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) -> - case ?file_write(Fd, Bin) of - ok -> - ok; - PrevWriteResult -> - %% don't report same error twice - PrevWriteResult; - Error -> - logger_h_common:error_notify({HandlerName,write,FileName,Error}), - Error +%% In order to play well with tools like logrotate, we need to be able +%% to re-create the file if it has disappeared (e.g. if rotated by +%% logrotate) +ensure(Fd,DevName) when is_atom(DevName) -> + Fd; +ensure(Fd,FileName) -> + case file:read_file_info(FileName) of + {ok,_} -> + Fd; + _ -> + _ = file:close(Fd), + _ = file:close(Fd), % delayed_write cause close not to close + case do_open_log_file({file,FileName}) of + {ok,Fd1} -> + Fd1; + Error -> + exit({could_not_reopen_file,Error}) + end end. -sync_dev(Fd, DevName, PrevSyncResult, HandlerName) -> - case ?file_datasync(Fd) of - ok -> - ok; - PrevSyncResult -> - %% don't report same error twice - PrevSyncResult; - Error -> - logger_h_common:error_notify({HandlerName,filesync,DevName,Error}), - Error - end. +write_to_dev(DevName, Bin, _DevName, _PrevWriteResult, _HandlerName) + when is_atom(DevName) -> + io:put_chars(DevName, Bin); +write_to_dev(Fd, Bin, FileName, PrevWriteResult, HandlerName) -> + Result = ?file_write(Fd, Bin), + maybe_notify_error(write,Result,PrevWriteResult,FileName,HandlerName). + +sync_dev(_Fd, _FileName, true, PrevSyncResult, _HandlerName) -> + PrevSyncResult; +sync_dev(Fd, FileName, false, PrevSyncResult, HandlerName) -> + Result = ?file_datasync(Fd), + maybe_notify_error(filesync,Result,PrevSyncResult,FileName,HandlerName). + +maybe_notify_error(_Op, ok, _PrevResult, _FileName, _HandlerName) -> + ok; +maybe_notify_error(_Op, PrevResult, PrevResult, _FileName, _HandlerName) -> + %% don't report same error twice + PrevResult; +maybe_notify_error(Op, Error, _PrevResult, FileName, HandlerName) -> + logger_h_common:error_notify({HandlerName,Op,FileName,Error}), + Error. diff --git a/lib/kernel/src/logger_sup.erl b/lib/kernel/src/logger_sup.erl index 3d6f482e20..9ea8558a16 100644 --- a/lib/kernel/src/logger_sup.erl +++ b/lib/kernel/src/logger_sup.erl @@ -50,7 +50,9 @@ init([]) -> start => {logger_handler_watcher, start_link, []}, shutdown => brutal_kill}, - {ok, {SupFlags, [Watcher]}}. + Proxy = logger_proxy:child_spec(), + + {ok, {SupFlags, [Watcher,Proxy]}}. %%%=================================================================== %%% Internal functions diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index 01d809b566..4915193196 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -808,7 +808,8 @@ handle_info({AcceptPid, {accept_pending,MyNode,Node,Address,Type}}, State) -> _:_ -> error_logger:error_msg("~n** Cannot get connection id for node ~w~n", [Node]), - AcceptPid ! {self(),{accept_pending,nok_pending}} + AcceptPid ! {self(),{accept_pending,nok_pending}}, + {noreply, State} end end; diff --git a/lib/kernel/src/seq_trace.erl b/lib/kernel/src/seq_trace.erl index 14fe21e9de..4f9d7b3e5c 100644 --- a/lib/kernel/src/seq_trace.erl +++ b/lib/kernel/src/seq_trace.erl @@ -98,7 +98,7 @@ print(Label, Term) -> -spec reset_trace() -> 'true'. reset_trace() -> - erlang:system_flag(1, 0). + erlang:system_flag(reset_seq_trace, true). %% reset_trace(Pid) -> % this might be a useful function too diff --git a/lib/kernel/test/Makefile b/lib/kernel/test/Makefile index 4a86265a4a..8a6ffe7e72 100644 --- a/lib/kernel/test/Makefile +++ b/lib/kernel/test/Makefile @@ -76,8 +76,11 @@ MODULES= \ logger_filters_SUITE \ logger_formatter_SUITE \ logger_legacy_SUITE \ + logger_olp_SUITE \ + logger_proxy_SUITE \ logger_simple_h_SUITE \ logger_std_h_SUITE \ + logger_stress_SUITE \ logger_test_lib \ os_SUITE \ pg2_SUITE \ diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl index 04c0c48e3a..52edfaee29 100644 --- a/lib/kernel/test/gen_tcp_misc_SUITE.erl +++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl @@ -53,7 +53,7 @@ active_once_closed/1, send_timeout/1, send_timeout_active/1, otp_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1, wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1, - otp_12242/1]). + otp_12242/1, delay_send_error/1]). %% Internal exports. -export([sender/3, not_owner/1, passive_sockets_server/2, priority_server/1, @@ -97,7 +97,7 @@ all() -> active_once_closed, send_timeout, send_timeout_active, otp_7731, wrapping_oct, zombie_sockets, otp_7816, otp_8102, otp_9389, - otp_12242]. + otp_12242, delay_send_error]. groups() -> []. @@ -1969,20 +1969,20 @@ recvtclass(_Config) -> {skip,{ipv6_not_supported,IFs}} end. -%% These version numbers are the highest noted in daily tests -%% where the test fails for a plausible reason, so -%% skip on that platform. +%% These version numbers are above the highest noted +%% in daily tests where the test fails for a plausible reason, +%% so skip on platforms of lower version, i.e they are future +%% versions where it is possible that it might not fail. %% -%% On newer versions it might be fixed, but we'll see about that -%% when machines with newer versions gets installed... -%% If the test still fails for a plausible reason these +%% When machines with newer versions gets installed, +%% if the test still fails for a plausible reason these %% version numbers simply should be increased. %% Or maybe we should change to only test on known good %% platforms - change {unix,_} to false? %% pktoptions is not supported for IPv4 recvtos_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); -recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); +recvtos_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); %% Using the option returns einval, so it is not implemented. recvtos_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); recvtos_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); @@ -1994,18 +1994,19 @@ recvtos_ok(_, _) -> false. %% pktoptions is not supported for IPv4 recvttl_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); -recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); +recvttl_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); %% Using the option returns einval, so it is not implemented. recvttl_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); recvttl_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); +%% Does not return any value - not implemented for pktoptions +recvttl_ok({unix,linux}, OSVer) -> not semver_lt(OSVer, {2,7,0}); %% -recvttl_ok({unix,linux}, _) -> true; recvttl_ok({unix,_}, _) -> true; recvttl_ok(_, _) -> false. %% pktoptions is not supported for IPv6 recvtclass_ok({unix,openbsd}, OSVer) -> not semver_lt(OSVer, {6,4,0}); -recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {17,6,0}); +recvtclass_ok({unix,darwin}, OSVer) -> not semver_lt(OSVer, {19,0,0}); recvtclass_ok({unix,sunos}, OSVer) -> not semver_lt(OSVer, {5,12,0}); %% Using the option returns einval, so it is not implemented. recvtclass_ok({unix,freebsd}, OSVer) -> not semver_lt(OSVer, {11,2,0}); @@ -3426,3 +3427,32 @@ otp_12242(Addr) when tuple_size(Addr) =:= 4 -> wait(Mref) -> receive {'DOWN',Mref,_,_,Reason} -> Reason end. + +%% OTP-15536 +%% Test that send error works correctly for delay_send +delay_send_error(Config) -> + {ok, LS} = gen_tcp:listen(0, [{reuseaddr, true}, {packet, 1}, {active, false}]), + {ok,{{0,0,0,0},PortNum}}=inet:sockname(LS), + P = spawn_link( + fun() -> + {ok, S} = gen_tcp:accept(LS), + receive die -> gen_tcp:close(S) end + end), + erlang:monitor(process, P), + {ok, S} = gen_tcp:connect("localhost", PortNum, + [{packet, 1}, {active, false}, {delay_send, true}]), + + %% Do a couple of sends first to see that it works + ok = gen_tcp:send(S, "hello"), + ok = gen_tcp:send(S, "hello"), + ok = gen_tcp:send(S, "hello"), + + %% Make the receiver close + P ! die, + receive _Down -> ok end, + + ok = gen_tcp:send(S, "hello"), + timer:sleep(500), %% Sleep in order for delay_send to have time to trigger + + %% This used to result in a double free + {error, closed} = gen_tcp:send(S, "hello"). diff --git a/lib/kernel/test/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl index 7828cc4716..a0154b2694 100644 --- a/lib/kernel/test/init_SUITE.erl +++ b/lib/kernel/test/init_SUITE.erl @@ -295,7 +295,7 @@ is_real_system(KernelVsn, StdlibVsn) -> %% before restart. %% ------------------------------------------------ many_restarts() -> - [{timetrap,{minutes,8}}]. + [{timetrap,{minutes,16}}]. many_restarts(Config) when is_list(Config) -> {ok, Node} = loose_node:start(init_test, "", ?DEFAULT_TIMEOUT_SEC), @@ -315,7 +315,7 @@ loop_restart(N,Node,EHPid) -> loose_node:stop(Node), ct:fail(not_stopping) end, - ok = wait_for(30, Node, EHPid), + ok = wait_for(60, Node, EHPid), loop_restart(N-1,Node,rpc:call(Node,erlang,whereis,[logger])). wait_for(0,Node,_) -> @@ -367,7 +367,8 @@ restart(Config) when is_list(Config) -> SysProcs0 = rpc:call(Node, ?MODULE, find_system_processes, []), io:format("SysProcs0=~p~n", [SysProcs0]), [InitPid, PurgerPid, LitCollectorPid, - DirtySigNPid, DirtySigHPid, DirtySigMPid] = SysProcs0, + DirtySigNPid, DirtySigHPid, DirtySigMPid, + PrimFilePid] = SysProcs0, InitPid = rpc:call(Node, erlang, whereis, [init]), PurgerPid = rpc:call(Node, erlang, whereis, [erts_code_purger]), Procs = rpc:call(Node, erlang, processes, []), @@ -385,7 +386,8 @@ restart(Config) when is_list(Config) -> SysProcs1 = rpc:call(Node, ?MODULE, find_system_processes, []), io:format("SysProcs1=~p~n", [SysProcs1]), [InitPid1, PurgerPid1, LitCollectorPid1, - DirtySigNPid1, DirtySigHPid1, DirtySigMPid1] = SysProcs1, + DirtySigNPid1, DirtySigHPid1, DirtySigMPid1, + PrimFilePid1] = SysProcs1, %% Still the same init process! InitPid1 = rpc:call(Node, erlang, whereis, [init]), @@ -411,6 +413,10 @@ restart(Config) when is_list(Config) -> DirtySigMP = pid_to_list(DirtySigMPid), DirtySigMP = pid_to_list(DirtySigMPid1), + %% and same prim_file helper process! + PrimFileP = pid_to_list(PrimFilePid), + PrimFileP = pid_to_list(PrimFilePid1), + NewProcs0 = rpc:call(Node, erlang, processes, []), NewProcs = NewProcs0 -- SysProcs1, case check_processes(NewProcs, MaxPid) of @@ -437,7 +443,8 @@ restart(Config) when is_list(Config) -> literal_collector, dirty_sig_handler_normal, dirty_sig_handler_high, - dirty_sig_handler_max}). + dirty_sig_handler_max, + prim_file}). find_system_processes() -> find_system_procs(processes(), #sys_procs{}). @@ -448,7 +455,8 @@ find_system_procs([], SysProcs) -> SysProcs#sys_procs.literal_collector, SysProcs#sys_procs.dirty_sig_handler_normal, SysProcs#sys_procs.dirty_sig_handler_high, - SysProcs#sys_procs.dirty_sig_handler_max]; + SysProcs#sys_procs.dirty_sig_handler_max, + SysProcs#sys_procs.prim_file]; find_system_procs([P|Ps], SysProcs) -> case process_info(P, [initial_call, priority]) of [{initial_call,{erl_init,start,2}},_] -> @@ -472,6 +480,9 @@ find_system_procs([P|Ps], SysProcs) -> {priority,max}] -> undefined = SysProcs#sys_procs.dirty_sig_handler_max, find_system_procs(Ps, SysProcs#sys_procs{dirty_sig_handler_max = P}); + [{initial_call,{prim_file,start,0}},_] -> + undefined = SysProcs#sys_procs.prim_file, + find_system_procs(Ps, SysProcs#sys_procs{prim_file = P}); _ -> find_system_procs(Ps, SysProcs) end. diff --git a/lib/kernel/test/kernel_bench.spec b/lib/kernel/test/kernel_bench.spec index 4de133f21b..898ceb59e0 100644 --- a/lib/kernel/test/kernel_bench.spec +++ b/lib/kernel/test/kernel_bench.spec @@ -1,2 +1,3 @@ {groups,"../kernel_test",zlib_SUITE,[bench]}. {groups,"../kernel_test",file_SUITE,[bench]}. +{suites,"../kernel_test",[logger_stress_SUITE]}. diff --git a/lib/kernel/test/logger.cover b/lib/kernel/test/logger.cover index 960bc0abff..9691aa295e 100644 --- a/lib/kernel/test/logger.cover +++ b/lib/kernel/test/logger.cover @@ -4,9 +4,12 @@ logger_backend, logger_config, logger_disk_log_h, - logger_h_common, logger_filters, logger_formatter, + logger_handler_watcher, + logger_h_common, + logger_olp, + logger_proxy, logger_server, logger_simple_h, logger_std_h, diff --git a/lib/kernel/test/logger.spec b/lib/kernel/test/logger.spec index 1ab90b3e93..3aec37951d 100644 --- a/lib/kernel/test/logger.spec +++ b/lib/kernel/test/logger.spec @@ -7,5 +7,7 @@ logger_filters_SUITE, logger_formatter_SUITE, logger_legacy_SUITE, + logger_olp_SUITE, + logger_proxy_SUITE, logger_simple_h_SUITE, logger_std_h_SUITE]}. diff --git a/lib/kernel/test/logger_disk_log_h_SUITE.erl b/lib/kernel/test/logger_disk_log_h_SUITE.erl index 905c2c52c5..9bbec42de8 100644 --- a/lib/kernel/test/logger_disk_log_h_SUITE.erl +++ b/lib/kernel/test/logger_disk_log_h_SUITE.erl @@ -24,6 +24,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/src/logger_internal.hrl"). +-include_lib("kernel/src/logger_olp.hrl"). -include_lib("kernel/src/logger_h_common.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/file.hrl"). @@ -97,7 +98,6 @@ all() -> formatter_fail, config_fail, bad_input, - info_and_reset, reconfig, sync, disk_log_full, @@ -306,9 +306,9 @@ logging(cleanup, _Config) -> filter_config(_Config) -> ok = logger:add_handler(?MODULE,logger_disk_log_h,#{}), {ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE), - HConfig = maps:without([handler_pid,mode_tab],HConfig), + HConfig = maps:without([olp],HConfig), - FakeFullHConfig = HConfig#{handler_pid=>self(),mode_tab=>erlang:make_ref()}, + FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}}, #{config:=HConfig} = logger_disk_log_h:filter_config(Config#{config=>FakeFullHConfig}), ok. @@ -332,16 +332,18 @@ errors(Config) -> %%! Check how bad log_opts are handled! {error,{illegal_config_change, - #{config:=#{type:=wrap}}, - #{config:=#{type:=halt}}}} = + logger_disk_log_h, + #{type:=wrap}, + #{type:=halt}}} = logger:update_handler_config(Name1, config, #{type=>halt, file=>LogFile1}), {error,{illegal_config_change, - #{config:=#{file:=LogFile1}}, - #{config:=#{file:="newfilename"}}}} = + logger_disk_log_h, + #{file:=LogFile1}, + #{file:="newfilename"}}} = logger:update_handler_config(Name1, config, #{file=>"newfilename"}), @@ -349,9 +351,7 @@ errors(Config) -> %% Read-only fields may (accidentially) be included in the change, %% but it won't take effect {ok,C} = logger:get_handler_config(Name1), - ok = logger:set_handler_config(Name1,config, - #{handler_pid=>self(), - mode_tab=>erlang:make_ref()}), + ok = logger:set_handler_config(Name1,config,#{olp=>dummyvalue}), {ok,C} = logger:get_handler_config(Name1), @@ -411,20 +411,22 @@ formatter_fail(cleanup,_Config) -> ok. config_fail(_Config) -> - {error,{handler_not_added,{invalid_config,logger_disk_log_h,{bad,bad}}}} = + {error,{handler_not_added,{invalid_config,logger_disk_log_h,#{bad:=bad}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{bad => bad}, filter_default=>log, formatter=>{?MODULE,self()}}), - {error,{handler_not_added,{invalid_levels,{_,1,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{drop_mode_qlen=>1}}), - {error,{handler_not_added,{invalid_levels,{43,42,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43, + drop_mode_qlen:=42}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{sync_mode_qlen=>43, drop_mode_qlen=>42}}), - {error,{handler_not_added,{invalid_levels,{_,43,42}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43, + flush_qlen:=42}}}} = logger:add_handler(?MODULE,logger_disk_log_h, #{config => #{drop_mode_qlen=>43, flush_qlen=>42}}), @@ -433,20 +435,17 @@ config_fail(_Config) -> #{filter_default=>log, formatter=>{?MODULE,self()}}), %% can't change the disk log options for a log already in use - {error,{illegal_config_change,_,_}} = + {error,{illegal_config_change,logger_disk_log_h,_,_}} = logger:update_handler_config(?MODULE,config, #{max_no_files=>2}), - %% can't change name of an existing handler - {error,{illegal_config_change,_,_}} = - logger:update_handler_config(?MODULE,id,bad), %% incorrect values of OP params {ok,#{config := HConfig}} = logger:get_handler_config(?MODULE), - {error,{invalid_levels,_}} = + {error,{invalid_olp_levels,_}} = logger:update_handler_config(?MODULE,config, HConfig#{sync_mode_qlen=>100, flush_qlen=>99}), %% invalid name of config parameter - {error,{invalid_config,logger_disk_log_h,{filesync_rep_int,2000}}} = + {error,{invalid_config,logger_disk_log_h,#{filesync_rep_int:=2000}}} = logger:update_handler_config(?MODULE, config, HConfig#{filesync_rep_int => 2000}), ok. @@ -455,18 +454,7 @@ config_fail(cleanup,_Config) -> bad_input(_Config) -> {error,{badarg,{filesync,["BadType"]}}} = - logger_disk_log_h:filesync("BadType"), - {error,{badarg,{info,["BadType"]}}} = logger_disk_log_h:info("BadType"), - {error,{badarg,{reset,["BadType"]}}} = logger_disk_log_h:reset("BadType"). - -info_and_reset(_Config) -> - ok = logger:add_handler(?MODULE,logger_disk_log_h, - #{filter_default=>log, - formatter=>{?MODULE,self()}}), - #{id := ?MODULE} = logger_disk_log_h:info(?MODULE), - ok = logger_disk_log_h:reset(?MODULE). -info_and_reset(cleanup,_Config) -> - logger:remove_handler(?MODULE). + logger_disk_log_h:filesync("BadType"). reconfig(Config) -> Dir = ?config(priv_dir,Config), @@ -475,7 +463,7 @@ reconfig(Config) -> #{filter_default=>log, filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,self()}}), - #{id := ?MODULE, + #{%id := ?MODULE, sync_mode_qlen := ?SYNC_MODE_QLEN, drop_mode_qlen := ?DROP_MODE_QLEN, flush_qlen := ?FLUSH_QLEN, @@ -486,12 +474,14 @@ reconfig(Config) -> overload_kill_qlen := ?OVERLOAD_KILL_QLEN, overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL, - log_opts := #{type := ?DISK_LOG_TYPE, - max_no_files := ?DISK_LOG_MAX_NO_FILES, - max_no_bytes := ?DISK_LOG_MAX_NO_BYTES, - file := DiskLogFile}} = - logger_disk_log_h:info(?MODULE), + cb_state := + #{handler_state := + #{log_opts := #{type := ?DISK_LOG_TYPE, + max_no_files := ?DISK_LOG_MAX_NO_FILES, + max_no_bytes := ?DISK_LOG_MAX_NO_BYTES, + file := DiskLogFile}}, + filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL}} = + logger_olp:info(h_proc_name()), {ok,#{config := #{sync_mode_qlen := ?SYNC_MODE_QLEN, drop_mode_qlen := ?DROP_MODE_QLEN, @@ -522,7 +512,7 @@ reconfig(Config) -> overload_kill_restart_after => infinity, filesync_repeat_interval => no_repeat}, ok = logger:set_handler_config(?MODULE, config, HConfig1), - #{id := ?MODULE, + #{%id := ?MODULE, sync_mode_qlen := 1, drop_mode_qlen := 2, flush_qlen := 3, @@ -533,8 +523,8 @@ reconfig(Config) -> overload_kill_qlen := 100000, overload_kill_mem_size := 10000000, overload_kill_restart_after := infinity, - filesync_repeat_interval := no_repeat} = - logger_disk_log_h:info(?MODULE), + cb_state := #{filesync_repeat_interval := no_repeat}} = + logger_olp:info(h_proc_name()), {ok,#{config:=HConfig1}} = logger:get_handler_config(?MODULE), ok = logger:update_handler_config(?MODULE, config, @@ -572,11 +562,13 @@ reconfig(Config) -> max_no_files => 1, max_no_bytes => 1024, file => File}}), - #{log_opts := #{type := halt, - max_no_files := 1, - max_no_bytes := 1024, - file := File}} = - logger_disk_log_h:info(?MODULE), + #{cb_state := + #{handler_state := + #{log_opts := #{type := halt, + max_no_files := 1, + max_no_bytes := 1024, + file := File}}}} = + logger_olp:info(h_proc_name()), {ok,#{config := #{type := halt, max_no_files := 1, @@ -596,13 +588,13 @@ reconfig(Config) -> %% You are not allowed to actively set the write once fields %% (type, max_no_files, max_no_bytes, file) in runtime. - {error, {illegal_config_change,_,_}} = + {error, {illegal_config_change,_,_,_}} = logger:set_handler_config(?MODULE,config,#{type=>wrap}), - {error, {illegal_config_change,_,_}} = + {error, {illegal_config_change,_,_,_}} = logger:set_handler_config(?MODULE,config,#{max_no_files=>2}), - {error, {illegal_config_change,_,_}} = + {error, {illegal_config_change,_,_,_}} = logger:set_handler_config(?MODULE,config,#{max_no_bytes=>2048}), - {error, {illegal_config_change,_,_}} = + {error, {illegal_config_change,_,_,_}} = logger:set_handler_config(?MODULE,config,#{file=>"otherfile.log"}), {ok,C7} = logger:get_handler_config(?MODULE), ct:log("C7: ~p",[C7]), @@ -630,56 +622,52 @@ sync(Config) -> filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,nl}}), - start_tracer([{disk_log,blog,2}, - {logger_disk_log_h,disk_log_sync,2}], - [{disk_log,blog,<<"first\n">>}, - {logger_disk_log_h,disk_log_sync}]), + start_tracer([{logger_disk_log_h,disk_log_write,3}, + {disk_log,sync,1}], + [{logger_disk_log_h,disk_log_write,<<"first\n">>}, + {disk_log,sync}]), logger:notice("first", ?domain), %% wait for automatic disk_log_sync check_tracer(?FILESYNC_REPEAT_INTERVAL*2), - %% check that if there's no repeated disk_log_sync active, + %% check that if there's no repeated filesync active, %% a disk_log_sync is still performed when handler goes idle {ok,#{config := HConfig}} = logger:get_handler_config(?MODULE), HConfig1 = HConfig#{filesync_repeat_interval => no_repeat}, ok = logger:update_handler_config(?MODULE, config, HConfig1), - no_repeat = maps:get(filesync_repeat_interval, - logger_disk_log_h:info(?MODULE)), - %% The following timer is to make sure the time from last log - %% ("first") to next ("second") is long enough, so the a flush is - %% triggered by the idle timeout between "fourth" and "fifth". - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), - - start_tracer([{disk_log,blog,2}, - {logger_disk_log_h,disk_log_sync,2}], - [{disk_log,blog,<<"second\n">>}, - {logger_disk_log_h,disk_log_sync}, - {disk_log,blog,<<"third\n">>}, - {logger_disk_log_h,disk_log_sync}]), + maps:get(cb_state,logger_olp:info(h_proc_name()))), + + start_tracer([{logger_disk_log_h,disk_log_write,3}, + {disk_log,sync,1}], + [{logger_disk_log_h,disk_log_write,<<"second\n">>}, + {disk_log,sync}, + {logger_disk_log_h,disk_log_write,<<"third\n">>}, + {disk_log,sync}]), logger:notice("second", ?domain), - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), + timer:sleep(?IDLE_DETECT_TIME*2), logger:notice("third", ?domain), %% wait for automatic disk_log_sync - check_tracer(?IDLE_DETECT_TIME_MSEC*2), + check_tracer(?IDLE_DETECT_TIME*2), try_read_file(Log, {ok,<<"first\nsecond\nthird\n">>}, 1000), - %% switch repeated disk_log_sync on and verify that the looping works + %% switch repeated filesync on and verify that the looping works SyncInt = 1000, WaitT = 4500, - OneSync = {logger_disk_log_h,handle_cast,repeated_disk_log_sync}, - %% receive 1 initial repeated_disk_log_sync, then 1 per sec - start_tracer([{logger_disk_log_h,handle_cast,2}], - [OneSync || _ <- lists:seq(1, 1 + trunc(WaitT/SyncInt))]), + OneSync = {logger_h_common,handle_cast,repeated_filesync}, + %% receive 1 repeated_filesync per sec + start_tracer([{{logger_h_common,handle_cast,2}, + [{[repeated_filesync,'_'],[],[{message,{caller}}]}]}], + [OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]), HConfig2 = HConfig#{filesync_repeat_interval => SyncInt}, ok = logger:update_handler_config(?MODULE, config, HConfig2), SyncInt = maps:get(filesync_repeat_interval, - logger_disk_log_h:info(?MODULE)), + maps:get(cb_state,logger_olp:info(h_proc_name()))), timer:sleep(WaitT), HConfig3 = HConfig#{filesync_repeat_interval => no_repeat}, ok = logger:update_handler_config(?MODULE, config, HConfig3), @@ -716,7 +704,7 @@ disk_log_wrap(Config) -> end, {ok,_} = dbg:tracer(process, {TraceFun, Tester}), {ok,_} = dbg:p(whereis(h_proc_name()), [c]), - {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []), + {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []), Text = [34 + rand:uniform(126-34) || _ <- lists:seq(1,MaxBytes)], ct:pal("String = ~p (~w)", [Text, erts_debug:size(Text)]), @@ -734,7 +722,7 @@ disk_log_wrap(Config) -> timer:sleep(1000), dbg:stop_clear(), Received = lists:flatmap(fun({trace,_M,handle_info, - [{disk_log,_Node,_Name,What},_]}) -> + [_,{disk_log,_Node,_Name,What},_]}) -> [{trace,What}]; ({log,_}) -> [] @@ -770,7 +758,7 @@ disk_log_full(Config) -> end, {ok,_} = dbg:tracer(process, {TraceFun, Tester}), {ok,_} = dbg:p(whereis(h_proc_name()), [c]), - {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []), + {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []), NoOfChars = 5, Text = [34 + rand:uniform(126-34) || _ <- lists:seq(1,NoOfChars)], @@ -780,20 +768,24 @@ disk_log_full(Config) -> timer:sleep(2000), dbg:stop_clear(), Received = lists:flatmap(fun({trace,_M,handle_info, - [{disk_log,_Node,_Name,What},_]}) -> + [_,{disk_log,_Node,_Name,What},_]}) -> [{trace,What}]; ({log,_}) -> [] end, test_server:messages_get()), ct:pal("Trace =~n~p", [Received]), - [{trace,full}, - {trace,{error_status,{error,{full,_}}}}] = Received, + + %% The tail here could be an error_status notification, if the + %% last write was synchronous, but in most cases it will not be + [{trace,full}|_] = Received, + %% [{trace,full}, + %% {trace,{error_status,{error,{full,_}}}}] = Received, ok. disk_log_full(cleanup, _Config) -> dbg:stop_clear(), logger:remove_handler(?MODULE). -disk_log_events(Config) -> +disk_log_events(_Config) -> Node = node(), Log = ?MODULE, ok = logger:add_handler(?MODULE, @@ -819,14 +811,14 @@ disk_log_events(Config) -> end, {ok,_} = dbg:tracer(process, {TraceFun, Tester}), {ok,_} = dbg:p(whereis(h_proc_name()), [c]), - {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 2, []), + {ok,_} = dbg:tp(logger_disk_log_h, handle_info, 3, []), [whereis(h_proc_name()) ! E || E <- Events], %% wait for trace messages timer:sleep(2000), dbg:stop_clear(), Received = lists:map(fun({trace,_M,handle_info, - [Got,_]}) -> Got + [_,Got,_]}) -> Got end, test_server:messages_get()), ct:pal("Trace =~n~p", [Received]), NoOfEvents = length(Events), @@ -849,13 +841,17 @@ write_failure(Config) -> false = (undefined == rpc:call(Node, ets, whereis, [?TEST_HOOKS_TAB])), rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]), rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]), - rpc:call(Node, ?MODULE, set_result, [disk_log_blog,ok]), - HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]), - ct:pal("LogOpts = ~p", [LogOpts = maps:get(log_opts, HState)]), - + rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]), + HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]), + LogOpts = maps:get(log_opts, + maps:get(handler_state, + maps:get(cb_state,HState))), + ct:pal("LogOpts = ~p", [LogOpts]), + + %% ?check and ?check_no_log in this test only check for internal log events ok = log_on_remote_node(Node, "Logged1"), rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]), - ?check_no_log, + ?check_no_log, % no internal log when write ok SyncRepInt = case (fun() -> is_atom(?FILESYNC_REPEAT_INTERVAL) end)() of true -> 5500; @@ -864,24 +860,26 @@ write_failure(Config) -> try_read_file(Log, {ok,<<"Logged1\n">>}, SyncRepInt), - rpc:call(Node, ?MODULE, set_result, [disk_log_blog,{error,no_such_log}]), + rpc:call(Node, ?MODULE, set_result, [disk_log_write,{error,no_such_log}]), ok = log_on_remote_node(Node, "Cause simple error printout"), + %% this should have caused an internal log ?check({error,{?STANDARD_HANDLER,log,LogOpts,{error,no_such_log}}}), - + ok = log_on_remote_node(Node, "No second error printout"), - ?check_no_log, + ?check_no_log, % but don't log same error twice - rpc:call(Node, ?MODULE, set_result, [disk_log_blog, + rpc:call(Node, ?MODULE, set_result, [disk_log_write, {error,{full,?STANDARD_HANDLER}}]), ok = log_on_remote_node(Node, "Cause simple error printout"), + %% this was a different error, so it should be logged ?check({error,{?STANDARD_HANDLER,log,LogOpts, {error,{full,?STANDARD_HANDLER}}}}), - rpc:call(Node, ?MODULE, set_result, [disk_log_blog,ok]), + rpc:call(Node, ?MODULE, set_result, [disk_log_write,ok]), ok = log_on_remote_node(Node, "Logged2"), rpc:call(Node, logger_disk_log_h, filesync, [?STANDARD_HANDLER]), - ?check_no_log, + ?check_no_log, % no internal log when write ok try_read_file(Log, {ok,<<"Logged1\nLogged2\n">>}, SyncRepInt), ok. write_failure(cleanup, _Config) -> @@ -900,15 +898,16 @@ sync_failure(Config) -> rpc:call(Node, ets, insert, [?TEST_HOOKS_TAB,{tester,self()}]), rpc:call(Node, ?MODULE, set_internal_log, [?MODULE,internal_log]), rpc:call(Node, ?MODULE, set_result, [disk_log_sync,ok]), - HState = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]), - LogOpts = maps:get(log_opts, HState), + HState = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]), + LogOpts = maps:get(log_opts, maps:get(handler_state, + maps:get(cb_state,HState))), SyncInt = 500, ok = rpc:call(Node, logger, update_handler_config, [?STANDARD_HANDLER, config, #{filesync_repeat_interval => SyncInt}]), - Info = rpc:call(Node, logger_disk_log_h, info, [?STANDARD_HANDLER]), - SyncInt = maps:get(filesync_repeat_interval, Info), + Info = rpc:call(Node, logger_olp, info, [h_proc_name(?STANDARD_HANDLER)]), + SyncInt = maps:get(filesync_repeat_interval, maps:get(cb_state, Info)), ok = log_on_remote_node(Node, "Logged1"), ?check_no_log, @@ -1184,7 +1183,7 @@ qlen_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1194,7 +1193,7 @@ qlen_kill_new(Config) -> ok after 5000 -> - Info = logger_disk_log_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1221,7 +1220,7 @@ mem_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1231,7 +1230,7 @@ mem_kill_new(Config) -> ok after 5000 -> - Info = logger_disk_log_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1257,7 +1256,7 @@ restart_after(Config) -> ok after 5000 -> - Info1 = logger_std_h:info(?MODULE), + Info1 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info1]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1281,7 +1280,7 @@ restart_after(Config) -> ok after 5000 -> - Info2 = logger_std_h:info(?MODULE), + Info2 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info2]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1302,11 +1301,15 @@ handler_requests_under_load(Config) -> flush_qlen => 2000, burst_limit_enable => false}}, ok = logger:update_handler_config(?MODULE, NewHConfig), - Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]}, - {info,[]}, - {reset,[]}, - {change_config,[]}]) - end), + Pid = spawn_link( + fun() -> send_requests(1,[{logger_disk_log_h,filesync,[?MODULE],[]}, + {logger_olp,info,[h_proc_name()],[]}, + {logger_olp,reset,[h_proc_name()],[]}, + {logger,update_handler_config, + [?MODULE, config, + #{overload_kill_enable => false}], + []}]) + end), Procs = 100, Sent = Procs * send_burst({n,5000}, {spawn,Procs,10}, {chars,79}, notice), Pid ! {self(),finish}, @@ -1318,29 +1321,22 @@ handler_requests_under_load(Config) -> [E || E <- Res, is_tuple(E) andalso (element(1,E) == error)] end, - Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult], - NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult), + Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult], + NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end, + 0, ReqResult), ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]), ok = file_delete(Log). handler_requests_under_load(cleanup, _Config) -> ok = stop_handler(?MODULE). -send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) -> +send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) -> receive {From,finish} -> From ! {self(),Reqs} after TO -> - Result = - case Req of - change_config -> - logger:update_handler_config(HName, logger_disk_log_h, - #{overload_kill_enable => - false}); - Func -> - logger_disk_log_h:Func(HName) - end, - send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}]) + Result = apply(Mod,Func,Args), + send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}]) end. %%%----------------------------------------------------------------- @@ -1458,15 +1454,6 @@ format(Msg,Tag) -> erlang:display(Error), exit(Error). -remove(Handler, LogName) -> - logger_disk_log_h:remove(Handler, LogName), - HState = #{log_names := Logs} = logger_disk_log_h:info(), - false = maps:is_key(LogName, HState), - false = lists:member(LogName, Logs), - false = logger_config:exist(?LOGGER_TABLE, LogName), - {error,no_such_log} = disk_log:info(LogName), - ok. - start_and_add(Name, Config, LogOpts) -> HConfig = maps:get(config, Config, #{}), HConfig1 = maps:merge(HConfig, LogOpts), @@ -1593,7 +1580,9 @@ start_tracer(Trace,Expected) -> ok. tpl([{M,F,A}|Trace]) -> - {ok,Match} = dbg:tpl(M,F,A,c), + tpl([{{M,F,A},c}|Trace]); +tpl([{{M,F,A},MS}|Trace]) -> + {ok,Match} = dbg:tpl(M,F,A,MS), case lists:keyfind(matched,1,Match) of {_,_,1} -> ok; @@ -1606,10 +1595,10 @@ tpl([{M,F,A}|Trace]) -> tpl([]) -> ok. -tracer({trace,_,call,{logger_disk_log_h,handle_cast,[Op|_]},Caller}, +tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]},Caller}, {Pid,[{Mod,Func,Op}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func,Op},Caller); -tracer({trace,_,call,{Mod=disk_log,Func=blog,[_,Data]},Caller}, {Pid,[{Mod,Func,Data}|Expected]}) -> +tracer({trace,_,call,{Mod=logger_disk_log_h,Func=disk_log_write,[_,_,Data]},Caller}, {Pid,[{Mod,Func,Data}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func,Data},Caller); tracer({trace,_,call,{Mod,Func,_},Caller}, {Pid,[{Mod,Func}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func},Caller); diff --git a/lib/kernel/test/logger_env_var_SUITE.erl b/lib/kernel/test/logger_env_var_SUITE.erl index e8d1a313dc..9d2ad11be8 100644 --- a/lib/kernel/test/logger_env_var_SUITE.erl +++ b/lib/kernel/test/logger_env_var_SUITE.erl @@ -59,7 +59,8 @@ groups() -> logger_undefined, logger_many_handlers_default_first, logger_many_handlers_default_last, - logger_many_handlers_default_last_broken_filter + logger_many_handlers_default_last_broken_filter, + logger_proxy ]}, {bad,[],[bad_error_logger, bad_level, @@ -541,6 +542,19 @@ logger_many_handlers(Config, Env, LogErr, LogInfo, NumProgress) -> ok. +logger_proxy(Config) -> + %% assume current node runs with default settings + DefOpts = logger_olp:get_opts(logger_proxy), + {ok,_,Node} = setup(Config, + [{logger,[{proxy,#{sync_mode_qlen=>0, + drop_mode_qlen=>2}}]}]), + Expected = DefOpts#{sync_mode_qlen:=0, + drop_mode_qlen:=2}, + Expected = rpc:call(Node,logger_olp,get_opts,[logger_proxy]), + Expected = rpc:call(Node,logger,get_proxy_config,[]), + + ok. + sasl_compatible_false(Config) -> Log = file(Config,?FUNCTION_NAME), {ok,_,Node} = setup(Config, diff --git a/lib/kernel/test/logger_olp_SUITE.erl b/lib/kernel/test/logger_olp_SUITE.erl new file mode 100644 index 0000000000..ea3eec89f5 --- /dev/null +++ b/lib/kernel/test/logger_olp_SUITE.erl @@ -0,0 +1,90 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(logger_olp_SUITE). + +-compile(export_all). + +-include_lib("kernel/src/logger_olp.hrl"). + +suite() -> + [{timetrap,{seconds,30}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. + +groups() -> + []. + +all() -> + [idle_timer]. + +%%%----------------------------------------------------------------- +%%% Test cases +idle_timer(_Config) -> + {ok,_Pid,Olp} = logger_olp:start_link(?MODULE,?MODULE,self(),#{}), + [logger_olp:load(Olp,{msg,N}) || N<-lists:seq(1,3)], + timer:sleep(?IDLE_DETECT_TIME*2), + [{load,{msg,1}}, + {load,{msg,2}}, + {load,{msg,3}}, + {notify,idle}] = test_server:messages_get(), + logger_olp:cast(Olp,hello), + timer:sleep(?IDLE_DETECT_TIME*2), + [{cast,hello}] = test_server:messages_get(), + ok. +idle_timer(cleanup,_Config) -> + unlink(whereis(?MODULE)), + logger_olp:stop(?MODULE), + ok. + +%%%----------------------------------------------------------------- +%%% Olp callbacks +init(P) -> + {ok,P}. + +handle_load(M,P) -> + P ! {load,M}, + P. + +handle_cast(M,P) -> + P ! {cast,M}, + {noreply,P}. + +notify(N,P) -> + P ! {notify,N}, + P. diff --git a/lib/kernel/test/logger_proxy_SUITE.erl b/lib/kernel/test/logger_proxy_SUITE.erl new file mode 100644 index 0000000000..777531e4ed --- /dev/null +++ b/lib/kernel/test/logger_proxy_SUITE.erl @@ -0,0 +1,274 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(logger_proxy_SUITE). + +-compile(export_all). + +%% -include_lib("common_test/include/ct.hrl"). +%% -include_lib("kernel/include/logger.hrl"). +%% -include_lib("kernel/src/logger_internal.hrl"). + +%% -define(str,"Log from "++atom_to_list(?FUNCTION_NAME)++ +%% ":"++integer_to_list(?LINE)). +%% -define(map_rep,#{function=>?FUNCTION_NAME, line=>?LINE}). +%% -define(keyval_rep,[{function,?FUNCTION_NAME}, {line,?LINE}]). + +%% -define(MY_LOC(N),#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY}, +%% file=>?FILE, line=>?LINE-N}). + +%% -define(TRY(X), my_try(fun() -> X end)). + + +-define(HNAME,list_to_atom(lists:concat([?MODULE,"_",?FUNCTION_NAME]))). +-define(LOC,#{mfa=>{?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY},line=>?LINE}). +-define(ENSURE_TIME,5000). + +suite() -> + [{timetrap,{seconds,30}}, + {ct_hooks,[logger_test_lib]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. + +groups() -> + []. + +all() -> + [basic, + emulator, + remote, + remote_emulator, + config, + restart_after, + terminate]. + +%%%----------------------------------------------------------------- +%%% Test cases +basic(_Config) -> + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + logger_proxy ! {log,notice,"Log from: ~p; ~p",[?FUNCTION_NAME,?LINE],L1=?LOC}, + ok = ensure(L1), + logger_proxy ! {log,notice,[{test_case,?FUNCTION_NAME},{line,?LINE}],L2=?LOC}, + ok = ensure(L2), + logger_proxy:log({remote,node(),{log,notice, + "Log from: ~p; ~p", + [?FUNCTION_NAME,?LINE], + L3=?LOC}}), + ok = ensure(L3), + logger_proxy:log({remote,node(),{log,notice, + [{test_case,?FUNCTION_NAME}, + {line,?LINE}], + L4=?LOC}}), + ok = ensure(L4), + ok. +basic(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +emulator(_Config) -> + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + Pid = spawn(fun() -> erlang:error(some_reason) end), + ok = ensure(#{pid=>Pid}), + ok. +emulator(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +remote(Config) -> + {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]), + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + L1 = ?LOC, spawn(Node,fun() -> logger:notice("Log from ~p; ~p",[?FUNCTION_NAME,?LINE],L1) end), + ok = ensure(L1), + L2 = ?LOC, spawn(Node,fun() -> logger:notice([{test_case,?FUNCTION_NAME},{line,?LINE}],L2) end), + ok = ensure(L2), + ok. +remote(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +remote_emulator(Config) -> + {ok,_,Node} = logger_test_lib:setup(Config,[{logger,[{proxy,#{}}]}]), + ok = logger:add_handler(?HNAME,?MODULE,#{config=>self()}), + Pid = spawn(Node,fun() -> erlang:error(some_reason) end), + ok = ensure(#{pid=>Pid}), + ok. +remote_emulator(cleanup,_Config) -> + ok = logger:remove_handler(?HNAME). + +config(_Config) -> + C1 = #{sync_mode_qlen:=SQ, + drop_mode_qlen:=DQ} = logger:get_proxy_config(), + C1 = logger_olp:get_opts(logger_proxy), + + %% Update the existing config with these two values + SQ1 = SQ+1, + DQ1 = DQ+1, + ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ1, + drop_mode_qlen=>DQ1}), + C2 = logger:get_proxy_config(), % reads from ets table + C2 = logger_olp:get_opts(logger_proxy), % ensure consistency with process opts + C2 = C1#{sync_mode_qlen:=SQ1, + drop_mode_qlen:=DQ1}, + + %% Update the existing again with only one value + SQ2 = SQ+2, + ok = logger:update_proxy_config(#{sync_mode_qlen=>SQ2}), + C3 = logger:get_proxy_config(), + C3 = logger_olp:get_opts(logger_proxy), + C3 = C2#{sync_mode_qlen:=SQ2}, + + %% Set the config, i.e. merge with defaults + ok = logger:set_proxy_config(#{sync_mode_qlen=>SQ1}), + C4 = logger:get_proxy_config(), + C4 = logger_olp:get_opts(logger_proxy), + C4 = C1#{sync_mode_qlen:=SQ1}, + + %% Reset to default + ok = logger:set_proxy_config(#{}), + C5 = logger:get_proxy_config(), + C5 = logger_olp:get_opts(logger_proxy), + C5 = logger_proxy:get_default_config(), + + %% Errors + {error,{invalid_olp_config,_}} = + logger:set_proxy_config(#{faulty_key=>1}), + {error,{invalid_olp_config,_}} = + logger:set_proxy_config(#{sync_mode_qlen=>infinity}), + {error,{invalid_config,[]}} = logger:set_proxy_config([]), + + {error,{invalid_olp_config,_}} = + logger:update_proxy_config(#{faulty_key=>1}), + {error,{invalid_olp_config,_}} = + logger:update_proxy_config(#{sync_mode_qlen=>infinity}), + {error,{invalid_config,[]}} = logger:update_proxy_config([]), + + C5 = logger:get_proxy_config(), + C5 = logger_olp:get_opts(logger_proxy), + + ok. +config(cleanup,_Config) -> + _ = logger:set_logger_proxy(logger_proxy:get_default_config()), + ok. + +restart_after(_Config) -> + Restart = 3000, + ok = logger:update_proxy_config(#{overload_kill_enable => true, + overload_kill_qlen => 10, + overload_kill_restart_after => Restart}), + Proxy = whereis(logger_proxy), + Proxy = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + Ref = erlang:monitor(process,Proxy), + spawn(fun() -> + [logger_proxy ! {log,debug, + [{test_case,?FUNCTION_NAME}, + {line,?LINE}], + ?LOC} || _ <- lists:seq(1,100)] + end), + receive + {'DOWN',Ref,_,_,_Reason} -> + undefined = erlang:system_info(system_logger), + timer:sleep(Restart), + poll_restarted(10) + after 5000 -> + ct:fail(proxy_not_terminated) + end, + + Proxy1 = whereis(logger_proxy), + Proxy1 = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + ok. +restart_after(cleanup,_Config) -> + _ = logger:set_logger_proxy(logger_proxy:get_default_config()), + ok. + +%% Test that system_logger flag is set to logger process if +%% logger_proxy terminates for other reason than overloaded. +terminate(_Config) -> + Logger = whereis(logger), + Proxy = whereis(logger_proxy), + Proxy = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + Ref = erlang:monitor(process,Proxy), + ok = logger_olp:stop(Proxy), + receive + {'DOWN',Ref,_,_,_Reason} -> + Logger = erlang:system_info(system_logger), + logger_proxy:restart(), + poll_restarted(10) + after 5000 -> + ct:fail(proxy_not_terminated) + end, + + Proxy1 = whereis(logger_proxy), + Proxy1 = erlang:system_info(system_logger), + ProxyConfig = logger:get_proxy_config(), + ProxyConfig = logger_olp:get_opts(logger_proxy), + + ok. + +%%%----------------------------------------------------------------- +%%% Internal functions + +poll_restarted(0) -> + ct:fail(proxy_not_restarted); +poll_restarted(N) -> + timer:sleep(1000), + case whereis(logger_proxy) of + undefined -> + poll_restarted(N-1); + _Pid -> + ok + end. + +%% Logger handler callback +log(#{meta:=Meta},#{config:=Pid}) -> + Pid ! {logged,Meta}. + +%% Check that the log from the logger callback function log/2 is received +ensure(Match) -> + receive {logged,Meta} -> + case maps:with(maps:keys(Match),Meta) of + Match -> ok; + _NoMatch -> {error,Match,Meta,test_server:messages_get()} + end + after ?ENSURE_TIME -> {error,Match,test_server:messages_get()} + end. diff --git a/lib/kernel/test/logger_std_h_SUITE.erl b/lib/kernel/test/logger_std_h_SUITE.erl index b6a09f4980..484d914ec3 100644 --- a/lib/kernel/test/logger_std_h_SUITE.erl +++ b/lib/kernel/test/logger_std_h_SUITE.erl @@ -25,10 +25,15 @@ -include_lib("kernel/include/logger.hrl"). -include_lib("kernel/src/logger_internal.hrl"). -include_lib("kernel/src/logger_h_common.hrl"). +-include_lib("kernel/src/logger_olp.hrl"). -include_lib("stdlib/include/ms_transform.hrl"). -include_lib("kernel/include/file.hrl"). --define(check_no_log, [] = test_server:messages_get()). +-define(check_no_log, + begin + timer:sleep(?IDLE_DETECT_TIME*2), + [] = test_server:messages_get() + end). -define(check(Expected), receive {log,Expected} -> @@ -115,7 +120,6 @@ all() -> crash_std_h_to_file, crash_std_h_to_disk_log, bad_input, - info_and_reset, reconfig, file_opts, sync, @@ -136,11 +140,12 @@ all() -> mem_kill_new, mem_kill_std, restart_after, - handler_requests_under_load + handler_requests_under_load, + recreate_deleted_log ]. add_remove_instance_tty(_Config) -> - {error,{handler_not_added,{invalid_config,logger_std_h,{type,tty}}}} = + {error,{handler_not_added,{invalid_config,logger_std_h,#{type:=tty}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{type => tty}, filter_default=>log, @@ -208,9 +213,9 @@ default_formatter(_Config) -> filter_config(_Config) -> ok = logger:add_handler(?MODULE,logger_std_h,#{}), {ok,#{config:=HConfig}=Config} = logger:get_handler_config(?MODULE), - HConfig = maps:without([handler_pid,mode_tab],HConfig), + HConfig = maps:without([olp],HConfig), - FakeFullHConfig = HConfig#{handler_pid=>self(),mode_tab=>erlang:make_ref()}, + FakeFullHConfig = HConfig#{olp=>{regname,self(),erlang:make_ref()}}, #{config:=HConfig} = logger_std_h:filter_config(Config#{config=>FakeFullHConfig}), ok. @@ -234,7 +239,7 @@ errors(Config) -> {error, {handler_not_added, - {invalid_config,logger_std_h,{type,faulty_type}}}} = + {invalid_config,logger_std_h,#{type:=faulty_type}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{type => faulty_type}}), @@ -245,13 +250,13 @@ errors(Config) -> _ -> NoDir = lists:concat(["/",?MODULE,"_dir"]), {error, - {handler_not_added,{{open_failed,NoDir,eacces},_}}} = + {handler_not_added,{open_failed,NoDir,eacces}}} = logger:add_handler(myh2,logger_std_h, #{config=>#{type=>{file,NoDir}}}) end, {error, - {handler_not_added,{{open_failed,Log,_},_}}} = + {handler_not_added,{open_failed,Log,_}}} = logger:add_handler(myh3,logger_std_h, #{config=>#{type=>{file,Log,[bad_file_opt]}}}), @@ -308,25 +313,27 @@ formatter_fail(cleanup,_Config) -> logger:remove_handler(?MODULE). config_fail(_Config) -> - {error,{handler_not_added,{invalid_config,logger_std_h,{bad,bad}}}} = + {error,{handler_not_added,{invalid_config,logger_std_h,#{bad:=bad}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{bad => bad}, filter_default=>log, formatter=>{?MODULE,self()}}), {error,{handler_not_added,{invalid_config,logger_std_h, - {restart_type,bad}}}} = + #{restart_type:=bad}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{restart_type => bad}, filter_default=>log, formatter=>{?MODULE,self()}}), - {error,{handler_not_added,{invalid_levels,{_,1,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=1}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{drop_mode_qlen=>1}}), - {error,{handler_not_added,{invalid_levels,{43,42,_}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{sync_mode_qlen:=43, + drop_mode_qlen:=42}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{sync_mode_qlen=>43, drop_mode_qlen=>42}}), - {error,{handler_not_added,{invalid_levels,{_,43,42}}}} = + {error,{handler_not_added,{invalid_olp_levels,#{drop_mode_qlen:=43, + flush_qlen:=42}}}} = logger:add_handler(?MODULE,logger_std_h, #{config => #{drop_mode_qlen=>43, flush_qlen=>42}}), @@ -334,24 +341,22 @@ config_fail(_Config) -> ok = logger:add_handler(?MODULE,logger_std_h, #{filter_default=>log, formatter=>{?MODULE,self()}}), - {error,{illegal_config_change,#{config:=#{type:=_}},#{config:=#{type:=_}}}} = + {error,{illegal_config_change,logger_std_h,#{type:=_},#{type:=_}}} = logger:set_handler_config(?MODULE,config, #{type=>{file,"file"}}), - {error,{invalid_levels,_}} = + {error,{invalid_olp_levels,_}} = logger:set_handler_config(?MODULE,config, #{sync_mode_qlen=>100, flush_qlen=>99}), - {error,{invalid_config,logger_std_h,{filesync_rep_int,2000}}} = + {error,{invalid_config,logger_std_h,#{filesync_rep_int:=2000}}} = logger:set_handler_config(?MODULE, config, #{filesync_rep_int => 2000}), %% Read-only fields may (accidentially) be included in the change, %% but it won't take effect {ok,C} = logger:get_handler_config(?MODULE), - ok = logger:set_handler_config(?MODULE,config, - #{handler_pid=>self(), - mode_tab=>erlang:make_ref()}), + ok = logger:set_handler_config(?MODULE,config,#{olp=>dummyvalue}), {ok,C} = logger:get_handler_config(?MODULE), ok. @@ -419,10 +424,13 @@ crash_std_h(Config,Func,Var,Type,Log) -> %% logger would send the log event to the logger process here instead %% of logging it itself. log_on_remote_node(Node,Msg) -> + Pid = self(), _ = spawn_link(Node, fun() -> erlang:group_leader(whereis(user),self()), - logger:notice(Msg) + logger:notice(Msg), + Pid ! done end), + receive done -> ok end, ok. @@ -450,14 +458,7 @@ sync_and_read(Node,file,Log) -> end. bad_input(_Config) -> - {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType"), - {error,{badarg,{info,["BadType"]}}} = logger_std_h:info("BadType"), - {error,{badarg,{reset,["BadType"]}}} = logger_std_h:reset("BadType"). - - -info_and_reset(_Config) -> - #{id := ?STANDARD_HANDLER} = logger_std_h:info(?STANDARD_HANDLER), - ok = logger_std_h:reset(?STANDARD_HANDLER). + {error,{badarg,{filesync,["BadType"]}}} = logger_std_h:filesync("BadType"). reconfig(Config) -> Dir = ?config(priv_dir,Config), @@ -467,9 +468,10 @@ reconfig(Config) -> filter_default=>log, filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,self()}}), - #{id := ?MODULE, - type := standard_io, - file_ctrl_pid := FileCtrlPid, + #{%id := ?MODULE, + cb_state:=#{handler_state := #{type := standard_io, + file_ctrl_pid := FileCtrlPid}, + filesync_repeat_interval := no_repeat}, sync_mode_qlen := ?SYNC_MODE_QLEN, drop_mode_qlen := ?DROP_MODE_QLEN, flush_qlen := ?FLUSH_QLEN, @@ -479,9 +481,8 @@ reconfig(Config) -> overload_kill_enable := ?OVERLOAD_KILL_ENABLE, overload_kill_qlen := ?OVERLOAD_KILL_QLEN, overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, - overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL} = DefaultInfo = - logger_std_h:info(?MODULE), + overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER} = + logger_olp:info(h_proc_name()), {ok, #{config:= @@ -496,7 +497,7 @@ reconfig(Config) -> overload_kill_qlen := ?OVERLOAD_KILL_QLEN, overload_kill_mem_size := ?OVERLOAD_KILL_MEM_SIZE, overload_kill_restart_after := ?OVERLOAD_KILL_RESTART_AFTER, - filesync_repeat_interval := ?FILESYNC_REPEAT_INTERVAL} = + filesync_repeat_interval := no_repeat} = DefaultHConf}} = logger:get_handler_config(?MODULE), @@ -511,10 +512,11 @@ reconfig(Config) -> overload_kill_qlen => 100000, overload_kill_mem_size => 10000000, overload_kill_restart_after => infinity, - filesync_repeat_interval => no_repeat}), - #{id := ?MODULE, - type := standard_io, - file_ctrl_pid := FileCtrlPid, + filesync_repeat_interval => 5000}), + #{%id := ?MODULE, + cb_state := #{handler_state := #{type := standard_io, + file_ctrl_pid := FileCtrlPid}, + filesync_repeat_interval := no_repeat}, sync_mode_qlen := 1, drop_mode_qlen := 2, flush_qlen := 3, @@ -524,8 +526,7 @@ reconfig(Config) -> overload_kill_enable := true, overload_kill_qlen := 100000, overload_kill_mem_size := 10000000, - overload_kill_restart_after := infinity, - filesync_repeat_interval := no_repeat} = Info = logger_std_h:info(?MODULE), + overload_kill_restart_after := infinity} = logger_olp:info(h_proc_name()), {ok,#{config := #{type := standard_io, @@ -584,7 +585,7 @@ reconfig(Config) -> %% You are not allowed to actively set 'type' in runtime, since %% this is a write once field. - {error, {illegal_config_change,_,_}} = + {error, {illegal_config_change,logger_std_h,_,_}} = logger:set_handler_config(?MODULE,config,#{type=>standard_io}), {ok,#{config:=C6}} = logger:get_handler_config(?MODULE), ct:log("C6: ~p",[C6]), @@ -607,7 +608,7 @@ file_opts(Config) -> Log = filename:join(Dir, lists:concat([?FUNCTION_NAME,".log"])), BadFileOpts = [raw], BadType = {file,Log,BadFileOpts}, - {error,{handler_not_added,{{open_failed,Log,enoent},_}}} = + {error,{handler_not_added,{open_failed,Log,enoent}}} = logger:add_handler(?MODULE, logger_std_h, #{config => #{type => BadType}}), @@ -620,7 +621,9 @@ file_opts(Config) -> filters=>?DEFAULT_HANDLER_FILTERS([?MODULE]), formatter=>{?MODULE,self()}}), - #{type := OkType} = logger_std_h:info(?MODULE), + #{cb_state := #{handler_state := #{type := OkType}}} = + logger_olp:info(h_proc_name()), + {ok,#{config := #{type := OkType}}} = logger:get_handler_config(?MODULE), logger:notice(M1=?msg,?domain), ?check(M1), B1 = ?bin(M1), @@ -643,10 +646,8 @@ sync(Config) -> %% check repeated filesync happens start_tracer([{logger_std_h, write_to_dev, 5}, - {logger_std_h, sync_dev, 4}, {file, datasync, 1}], [{logger_std_h, write_to_dev, <<"first\n">>}, - {logger_std_h, sync_dev}, {file,datasync}]), logger:notice("first", ?domain), @@ -655,10 +656,8 @@ sync(Config) -> %% check that explicit filesync is only done once start_tracer([{logger_std_h, write_to_dev, 5}, - {logger_std_h, sync_dev, 4}, {file, datasync, 1}], [{logger_std_h, write_to_dev, <<"second\n">>}, - {logger_std_h, sync_dev}, {file,datasync}, {no_more,500} ]), @@ -673,38 +672,34 @@ sync(Config) -> %% a filesync is still performed when handler goes idle ok = logger:update_handler_config(?MODULE, config, #{filesync_repeat_interval => no_repeat}), - no_repeat = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)), - %% The following timer is to make sure the time from last log - %% ("second") to next ("third") is long enough, so the a flush is - %% triggered by the idle timeout between "thrid" and "fourth". - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), + no_repeat = maps:get(filesync_repeat_interval, + maps:get(cb_state, logger_olp:info(h_proc_name()))), start_tracer([{logger_std_h, write_to_dev, 5}, - {logger_std_h, sync_dev, 4}, {file, datasync, 1}], [{logger_std_h, write_to_dev, <<"third\n">>}, - {logger_std_h, sync_dev}, {file,datasync}, {logger_std_h, write_to_dev, <<"fourth\n">>}, - {logger_std_h, sync_dev}, {file,datasync}]), logger:notice("third", ?domain), %% wait for automatic filesync - timer:sleep(?IDLE_DETECT_TIME_MSEC*2), + timer:sleep(?IDLE_DETECT_TIME*2), logger:notice("fourth", ?domain), %% wait for automatic filesync - check_tracer(?IDLE_DETECT_TIME_MSEC*2), + check_tracer(?IDLE_DETECT_TIME*2), %% switch repeated filesync on and verify that the looping works SyncInt = 1000, WaitT = 4500, - OneSync = {logger_std_h,handle_cast,repeated_filesync}, - %% receive 1 initial repeated_filesync, then 1 per sec - start_tracer([{logger_std_h,handle_cast,2}], - [OneSync || _ <- lists:seq(1, 1 + trunc(WaitT/SyncInt))]), + OneSync = {logger_h_common,handle_cast,repeated_filesync}, + %% receive 1 repeated_filesync per sec + start_tracer([{{logger_h_common,handle_cast,2}, + [{[repeated_filesync,'_'],[],[]}]}], + [OneSync || _ <- lists:seq(1, trunc(WaitT/SyncInt))]), ok = logger:update_handler_config(?MODULE, config, #{filesync_repeat_interval => SyncInt}), - SyncInt = maps:get(filesync_repeat_interval, logger_std_h:info(?MODULE)), + SyncInt = maps:get(filesync_repeat_interval, + maps:get(cb_state,logger_olp:info(h_proc_name()))), timer:sleep(WaitT), ok = logger:update_handler_config(?MODULE, config, #{filesync_repeat_interval => no_repeat}), @@ -765,8 +760,6 @@ sync_failure(Config) -> ok = rpc:call(Node, logger, update_handler_config, [?STANDARD_HANDLER, config, #{filesync_repeat_interval => SyncInt}]), - Info = rpc:call(Node, logger_std_h, info, [?STANDARD_HANDLER]), - SyncInt = maps:get(filesync_repeat_interval, Info), ok = log_on_remote_node(Node, "Logged1"), ?check_no_log, @@ -1096,7 +1089,7 @@ qlen_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1106,7 +1099,7 @@ qlen_kill_new(Config) -> ok after 5000 -> - Info = logger_std_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1147,7 +1140,7 @@ mem_kill_new(Config) -> receive {'DOWN', MRef, _, _, Info} -> case Info of - {shutdown,{overloaded,?MODULE,QLen,Mem}} -> + {shutdown,{overloaded,QLen,Mem}} -> ct:pal("Terminated with qlen = ~w, mem = ~w", [QLen,Mem]); killed -> ct:pal("Slow shutdown, handler process was killed!", []) @@ -1157,7 +1150,7 @@ mem_kill_new(Config) -> ok after 5000 -> - Info = logger_std_h:info(?MODULE), + Info = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info]), ct:fail("Handler not dead! It should not have survived this!") end. @@ -1188,7 +1181,7 @@ restart_after(Config) -> ok after 5000 -> - Info1 = logger_std_h:info(?MODULE), + Info1 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info1]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1213,7 +1206,7 @@ restart_after(Config) -> ok after 5000 -> - Info2 = logger_std_h:info(?MODULE), + Info2 = logger_olp:info(h_proc_name()), ct:pal("Handler state = ~p", [Info2]), ct:fail("Handler not dead! It should not have survived this!") end, @@ -1235,11 +1228,15 @@ handler_requests_under_load(Config) -> flush_qlen => 2000, burst_limit_enable => false}}, ok = logger:update_handler_config(?MODULE, NewHConfig), - Pid = spawn_link(fun() -> send_requests(?MODULE, 1, [{filesync,[]}, - {info,[]}, - {reset,[]}, - {change_config,[]}]) - end), + Pid = spawn_link( + fun() -> send_requests(1,[{logger_std_h,filesync,[?MODULE],[]}, + {logger_olp,info,[h_proc_name()],[]}, + {logger_olp,reset,[h_proc_name()],[]}, + {logger,update_handler_config, + [?MODULE, config, + #{overload_kill_enable => false}], + []}]) + end), Sent = send_burst({t,10000}, seq, {chars,79}, notice), Pid ! {self(),finish}, ReqResult = receive {Pid,Result} -> Result end, @@ -1250,36 +1247,45 @@ handler_requests_under_load(Config) -> [E || E <- Res, is_tuple(E) andalso (element(1,E) == error)] end, - Errors = [{Req,FindError(Res)} || {Req,Res} <- ReqResult], - NoOfReqs = lists:foldl(fun({_,Res}, N) -> N + length(Res) end, 0, ReqResult), + Errors = [{Func,FindError(Res)} || {_,Func,_,Res} <- ReqResult], + NoOfReqs = lists:foldl(fun({_,_,_,Res}, N) -> N + length(Res) end, + 0, ReqResult), ct:pal("~w requests made. Errors: ~n~p", [NoOfReqs,Errors]), ok = file_delete(Log). handler_requests_under_load(cleanup, _Config) -> ok = stop_handler(?MODULE). -send_requests(HName, TO, Reqs = [{Req,Res}|Rs]) -> +recreate_deleted_log(Config) -> + {Log,_HConfig,_StdHConfig} = + start_handler(?MODULE, ?FUNCTION_NAME, Config), + logger:notice("first",?domain), + logger_std_h:filesync(?MODULE), + ok = file:rename(Log,Log++".old"), + logger:notice("second",?domain), + logger_std_h:filesync(?MODULE), + {ok,<<"first\n">>} = file:read_file(Log++".old"), + {ok,<<"second\n">>} = file:read_file(Log), + ok. +recreate_deleted_log(cleanup, _Config) -> + ok = stop_handler(?MODULE). + +%%%----------------------------------------------------------------- +%%% +send_requests(TO, Reqs = [{Mod,Func,Args,Res}|Rs]) -> receive {From,finish} -> From ! {self(),Reqs} after TO -> - Result = - case Req of - change_config -> - logger:update_handler_config(HName, config, - #{overload_kill_enable => - false}); - Func -> - logger_std_h:Func(HName) - end, - send_requests(HName, TO, Rs ++ [{Req,[Result|Res]}]) + Result = apply(Mod,Func,Args), + send_requests(TO, Rs ++ [{Mod,Func,Args,[Result|Res]}]) end. %%%----------------------------------------------------------------- %%% -start_handler(Name, TTY, Config) when TTY == standard_io; - TTY == standard_error-> +start_handler(Name, TTY, _Config) when TTY == standard_io; + TTY == standard_error-> ok = logger:add_handler(Name, logger_std_h, #{config => #{type => TTY}, @@ -1533,7 +1539,7 @@ start_op_trace() -> {ok,_} = dbg:p(self(), [c]), MS1 = dbg:fun2ms(fun([_]) -> return_trace() end), - {ok,_} = dbg:tp(logger_h_common, check_load, 1, MS1), + {ok,_} = dbg:tpl(logger_h_common, check_load, 1, MS1), {ok,_} = dbg:tpl(logger_h_common, flush_log_requests, 2, []), @@ -1607,7 +1613,10 @@ analyse(Msgs) -> start_tracer(Trace,Expected) -> Pid = self(), - FileCtrlPid = maps:get(file_ctrl_pid, logger_std_h:info(?MODULE)), + FileCtrlPid = maps:get(file_ctrl_pid, + maps:get(handler_state, + maps:get(cb_state, + logger_olp:info(h_proc_name())))), dbg:tracer(process,{fun tracer/2,{Pid,Expected}}), dbg:p(whereis(h_proc_name()),[c]), dbg:p(FileCtrlPid,[c]), @@ -1615,7 +1624,9 @@ start_tracer(Trace,Expected) -> ok. tpl([{M,F,A}|Trace]) -> - {ok,Match} = dbg:tpl(M,F,A,[]), + tpl([{{M,F,A},[]}|Trace]); +tpl([{{M,F,A},MS}|Trace]) -> + {ok,Match} = dbg:tpl(M,F,A,MS), case lists:keyfind(matched,1,Match) of {_,_,1} -> ok; @@ -1628,7 +1639,7 @@ tpl([{M,F,A}|Trace]) -> tpl([]) -> ok. -tracer({trace,_,call,{logger_std_h,handle_cast,[Op|_]}}, +tracer({trace,_,call,{logger_h_common,handle_cast,[Op|_]}}, {Pid,[{Mod,Func,Op}|Expected]}) -> maybe_tracer_done(Pid,Expected,{Mod,Func,Op}); tracer({trace,_,call,{Mod=logger_std_h,Func=write_to_dev,[_,Data,_,_,_]}}, diff --git a/lib/kernel/test/logger_stress_SUITE.erl b/lib/kernel/test/logger_stress_SUITE.erl new file mode 100644 index 0000000000..4072e8c86a --- /dev/null +++ b/lib/kernel/test/logger_stress_SUITE.erl @@ -0,0 +1,456 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% 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(logger_stress_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct_event.hrl"). +-include_lib("kernel/include/logger.hrl"). +-include_lib("kernel/src/logger_h_common.hrl"). + +-ifdef(SAVE_STATS). + -define(COLLECT_STATS(_All_,_Procs_), + ct:pal("~p",[stats(_All_,_Procs_)])). +-else. + -define(COLLECT_STATS(_All_,_Procs__), ok). +-endif. + +-define(TEST_DURATION,120). % seconds + +suite() -> + [{timetrap,{minutes,3}}, + {ct_hooks,[logger_test_lib]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(_Group, Config) -> + Config. + +end_per_group(_Group, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(Case, Config) -> + try apply(?MODULE,Case,[cleanup,Config]) + catch error:undef -> ok + end, + ok. + +groups() -> + []. + +all() -> + [allow_events, + reject_events, + std_handler, + disk_log_handler, + emulator_events, + remote_events, + remote_to_disk_log, + remote_emulator_events, + remote_emulator_to_disk_log]. + +%%%----------------------------------------------------------------- +%%% Test cases +%%%----------------------------------------------------------------- +%% Time from log macro call to handler callback +allow_events(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,?MODULE,#{}}]}, + {logger_level,notice}]), + N = 100000, + {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end), + IOPS = N * 1000/T, % log events allowed per millisecond + ct_event:notify(#event{name = benchmark_data, + data = [{value,IOPS}]}), + {comment,io_lib:format("~.2f accepted events pr millisecond", + [IOPS])}. + +%% Time from log macro call to reject (log level) +reject_events(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,?MODULE,#{}}]}, + {logger_level,error}]), + N = 1000000, + {T,_} = timer:tc(fun() -> rpc:call(Node,?MODULE,nlogs,[N]) end), + IOPS = N * 1000/T, % log events rejected per millisecond + ct_event:notify(#event{name = benchmark_data, + data = [{value,IOPS}]}), + {comment,io_lib:format("~.2f rejected events pr millisecond", + [IOPS])}. + +%% Cascading failure that produce gen_server and proc_lib reports - +%% how many of the produced log events are actually written to a log +%% with logger_std_h file handler. +std_handler(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,logger_std_h, + #{config=>#{type=>{file,"default.log"}}}}]}]), + + cascade({Node,{logger_backend,log_allowed,2},[]}, + {Node,{logger_std_h,write,4},[{default,logger_std_h_default}]}, + fun otp_cascading/0). +std_handler(cleanup,_Config) -> + _ = file:delete("default.log"), + ok. + +%% Cascading failure that produce gen_server and proc_lib reports - +%% how many of the produced log events are actually written to a log +%% with logger_disk_log_h wrap file handler. +disk_log_handler(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,logger_disk_log_h,#{}}]}]), + cascade({Node,{logger_backend,log_allowed,2},[]}, + {Node,{logger_disk_log_h,write,4}, + [{default,logger_disk_log_h_default}]}, + fun otp_cascading/0). +disk_log_handler(cleanup,_Config) -> + Files = filelib:wildcard("default.log.*"), + [_ = file:delete(F) || F <- Files], + ok. + +%% Cascading failure that produce log events from the emulator - how +%% many of the produced log events pass through the proxy. +emulator_events(Config) -> + {ok,_,Node} = + logger_test_lib:setup(Config, + [{logger, + [{handler,default,?MODULE,#{}}]}]), + cascade({Node,{?MODULE,producer,0},[]}, + {Node,{?MODULE,log,2},[{proxy,logger_proxy}]}, + fun em_cascading/0). + +%% Cascading failure that produce gen_server and proc_lib reports on +%% remote node - how many of the produced log events pass through the +%% proxy. +remote_events(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,?MODULE,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]}, + {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]}, + fun otp_cascading/0). + +%% Cascading failure that produce gen_server and proc_lib reports on +%% remote node - how many of the produced log events are actually +%% written to a log with logger_disk_log_h wrap file handler. +remote_to_disk_log(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,logger_disk_log_h,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{logger_backend,log_allowed,2},[{remote_proxy,logger_proxy}]}, + {Node1,{logger_disk_log_h,write,4}, + [{local_proxy,logger_proxy}, + {local_default,logger_disk_log_h_default}]}, + fun otp_cascading/0). +remote_to_disk_log(cleanup,_Config) -> + Files = filelib:wildcard("default.log.*"), + [_ = file:delete(F) || F <- Files], + ok. + +%% Cascading failure that produce log events from the emulator on +%% remote node - how many of the produced log events pass through the +%% proxy. +remote_emulator_events(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,?MODULE,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]}, + {Node1,{?MODULE,log,2},[{local_proxy,logger_proxy}]}, + fun em_cascading/0). + +%% Cascading failure that produce log events from the emulator on +%% remote node - how many of the produced log events are actually +%% written to a log with logger_disk_log_h wrap file handler. +remote_emulator_to_disk_log(Config) -> + {ok,_,Node1} = + logger_test_lib:setup([{postfix,1}|Config], + [{logger, + [{handler,default,logger_disk_log_h,#{}}]}]), + {ok,_,Node2} = + logger_test_lib:setup([{postfix,2}|Config],[]), + cascade({Node2,{?MODULE,producer,0},[{remote_proxy,logger_proxy}]}, + {Node1,{logger_disk_log_h,write,4}, + [{local_proxy,logger_proxy}, + {local_default,logger_disk_log_h_default}]}, + fun em_cascading/0). +remote_emulator_to_disk_log(cleanup,_Config) -> + Files = filelib:wildcard("default.log.*"), + [_ = file:delete(F) || F <- Files], + ok. + +%%%----------------------------------------------------------------- +%%% Internal functions +nlogs(N) -> + group_leader(whereis(user),self()), + Str = "\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "[\\]^_`abcdefghijklmnopqr", + [?LOG_NOTICE(Str) || _ <- lists:seq(1,N)], + ok. + +%% cascade(ProducerInfo,ConsumerInfo,TestFun) +cascade({PNode,PMFA,_PStatProcs},{CNode,CMFA,_CStatProcs},TestFun) -> + Tab = ets:new(counter,[set,public]), + ets:insert(Tab,{producer,0}), + ets:insert(Tab,{consumer,0}), + dbg:tracer(process,{fun tracer/2,{Tab,PNode,CNode}}), + dbg:n(PNode), + dbg:n(CNode), + dbg:cn(node()), + dbg:p(all,[call,arity]), + dbg:tpl(PMFA,[]), + dbg:tpl(CMFA,[]), + + Pid = rpc:call(CNode,?MODULE,wrap_test,[PNode,TestFun]), + MRef = erlang:monitor(process,Pid), + TO = ?TEST_DURATION*1000, + receive {'DOWN',MRef,_,_,Reason} -> + ct:fail({remote_pid_down,Reason}) + after TO -> + All = ets:lookup_element(Tab,producer,2), + Written = ets:lookup_element(Tab,consumer,2), + dbg:stop_clear(), + ?COLLECT_STATS(All, + [{PNode,P,Id} || {Id,P} <- _PStatProcs] ++ + [{CNode,P,Id} || {Id,P} <- _CStatProcs]), + Ratio = Written/All * 100, + ct_event:notify(#event{name = benchmark_data, + data = [{value,Ratio}]}), + {comment,io_lib:format("~p % (~p written, ~p produced)", + [round(Ratio),Written,All])} + end. + +wrap_test(Fun) -> + wrap_test(node(),Fun). +wrap_test(Node,Fun) -> + reset(), + group_leader(whereis(user),self()), + rpc:call(Node,?MODULE,do_fun,[Fun]). + +do_fun(Fun) -> + reset(), + Fun(). + +reset() -> + reset([logger_std_h_default, logger_disk_log_h_default, logger_proxy]). +reset([P|Ps]) -> + is_pid(whereis(P)) andalso logger_olp:reset(P), + reset(Ps); +reset([]) -> + ok. + + +tracer({trace,_,call,{?MODULE,producer,_}},{Tab,_PNode,_CNode}=S) -> + ets:update_counter(Tab,producer,1), + S; +tracer({trace,Pid,call,{logger_backend,log_allowed,_}},{Tab,PNode,_CNode}=S) when node(Pid)=:=PNode -> + ets:update_counter(Tab,producer,1), + S; +tracer({trace,_,call,{?MODULE,log,_}},{Tab,_PNode,_CNode}=S) -> + ets:update_counter(Tab,consumer,1), + S; +tracer({trace,_,call,{_,write,_}},{Tab,_PNode,_CNode}=S) -> + ets:update_counter(Tab,consumer,1), + S; +tracer(_,S) -> + S. + + +%%%----------------------------------------------------------------- +%%% Collect statistics +-define(STAT_KEYS, + [burst_drops, + calls, + casts, + drops, + flushed, + flushes, + freq, + last_qlen, + max_qlen, + time, + writes]). +-define(EVENT_KEYS, + [calls,casts,flushed]). + +stats(All,Procs) -> + NI = [{Id,rpc:call(N,logger_olp,info,[P])} || {N,P,Id}<-Procs], + [{all,All}|[stats(Id,I,All) || {Id,I} <- NI]]. + +stats(Id,Info,All) -> + S = maps:with(?STAT_KEYS,Info), + AllOnProc = lists:sum(maps:values(maps:with(?EVENT_KEYS,S))), + if All>0 -> + Writes = maps:get(writes,S), + {_,ActiveTime} = maps:get(time,S), + Rate = round(100*Writes/All), + RateOnProc = + if AllOnProc>0 -> + round(100*Writes/AllOnProc); + true -> + 0 + end, + AvFreq = + if ActiveTime>0 -> + round(Writes/ActiveTime); + true -> + 0 + end, + {Id, + {stats,S}, + {rate,Rate}, + {rate_on_proc,RateOnProc}, + {av_freq,AvFreq}}; + true -> + {Id,none} + end. + +%%%----------------------------------------------------------------- +%%% Spawn a lot of processes that crash repeatedly, causing a lot of +%%% error reports from the emulator. +em_cascading() -> + spawn(fun() -> super() end). + +super() -> + process_flag(trap_exit,true), + spawn_link(fun server/0), + [spawn_link(fun client/0) || _<-lists:seq(1,10000)], + super_loop(). + +super_loop() -> + receive + {'EXIT',_,server} -> + spawn_link(fun server/0), + super_loop(); + {'EXIT',_,_} -> + _L = lists:sum(lists:seq(1,10000)), + spawn_link(fun client/0), + super_loop() + end. + +client() -> + receive + after 1 -> + case whereis(server) of + Pid when is_pid(Pid) -> + ok; + undefined -> + producer(), + erlang:error(some_exception) + end + end, + client(). + +server() -> + register(server,self()), + receive + after 3000 -> + exit(server) + end. + + +%%%----------------------------------------------------------------- +%%% Create a supervisor tree with processes that crash repeatedly, +%%% causing a lot of supervisor reports and crashreports +otp_cascading() -> + {ok,Pid} = supervisor:start_link({local,otp_super}, ?MODULE, [otp_super]), + unlink(Pid), + Pid. + +otp_server_sup() -> + supervisor:start_link({local,otp_server_sup},?MODULE,[otp_server_sup]). + +otp_client_sup(N) -> + supervisor:start_link({local,otp_client_sup},?MODULE,[otp_client_sup,N]). + +otp_server() -> + gen_server:start_link({local,otp_server},?MODULE,[otp_server],[]). + +otp_client() -> + gen_server:start_link(?MODULE,[otp_client],[]). + +init([otp_super]) -> + {ok, {{one_for_one, 200, 10}, + [{client_sup, + {?MODULE, otp_client_sup, [10000]}, + permanent, 1000, supervisor, [?MODULE]}, + {server_sup, + {?MODULE, otp_server_sup, []}, + permanent, 1000, supervisor, [?MODULE]} + ]}}; +init([otp_server_sup]) -> + {ok, {{one_for_one, 2, 10}, + [{server, + {?MODULE, otp_server, []}, + permanent, 1000, worker, [?MODULE]} + ]}}; +init([otp_client_sup,N]) -> + spawn(fun() -> + [supervisor:start_child(otp_client_sup,[]) + || _ <- lists:seq(1,N)] + end), + {ok, {{simple_one_for_one, N*10, 1}, + [{client, + {?MODULE, otp_client, []}, + permanent, 1000, worker, [?MODULE]} + ]}}; +init([otp_server]) -> + {ok, server, 10000}; +init([otp_client]) -> + {ok, client,1}. + +handle_info(timeout, client) -> + true = is_pid(whereis(otp_server)), + {noreply,client,1}; +handle_info(timeout, server) -> + exit(self(), some_error). + +%%%----------------------------------------------------------------- +%%% Logger callbacks +log(_LogEvent,_Config) -> + ok. + +%%%----------------------------------------------------------------- +%%% Function to trace on for counting produced emulator messages +producer() -> + ok. diff --git a/lib/kernel/test/logger_test_lib.erl b/lib/kernel/test/logger_test_lib.erl index 81eb9ce5eb..be4bc427fb 100644 --- a/lib/kernel/test/logger_test_lib.erl +++ b/lib/kernel/test/logger_test_lib.erl @@ -28,11 +28,17 @@ post_end_per_testcase/5, post_end_per_suite/3]). setup(Config,Vars) -> + Postfix = case proplists:get_value(postfix, Config) of + undefined -> ""; + P -> ["_",P] + end, FuncStr = lists:concat([proplists:get_value(suite, Config), "_", - proplists:get_value(tc, Config)]), + proplists:get_value(tc, Config)| + Postfix]), ConfigFileName = filename:join(proplists:get_value(priv_dir, Config), FuncStr), file:write_file(ConfigFileName ++ ".config", io_lib:format("[{kernel, ~p}].",[Vars])), - case test_server:start_node(proplists:get_value(tc, Config), slave, + Sname = lists:concat([proplists:get_value(tc,Config)|Postfix]), + case test_server:start_node(Sname, slave, [{args, ["-pa ",filename:dirname(code:which(?MODULE)), " -boot start_sasl -kernel start_timer true " "-config ",ConfigFileName]}]) of diff --git a/lib/kernel/test/sendfile_SUITE.erl b/lib/kernel/test/sendfile_SUITE.erl index 0c0b1cbcb6..ad060aa05c 100644 --- a/lib/kernel/test/sendfile_SUITE.erl +++ b/lib/kernel/test/sendfile_SUITE.erl @@ -341,7 +341,21 @@ t_sendfile_closeduring(Config) -> -1 end, - ok = sendfile_send({127,0,0,1}, Send, 0). + ok = sendfile_send({127,0,0,1}, Send, 0, [{active,false}]), + [] = flush(), + ok = sendfile_send({127,0,0,1}, Send, 0, [{active,true}]), + [] = flush(), + ok. + +flush() -> + lists:reverse(flush([])). + +flush(Acc) -> + receive M -> + flush([M | Acc]) + after 0 -> + Acc + end. t_sendfile_crashduring(Config) -> Filename = proplists:get_value(big_file, Config), @@ -409,12 +423,16 @@ sendfile_send(Send) -> sendfile_send(Host, Send) -> sendfile_send(Host, Send, []). sendfile_send(Host, Send, Orig) -> + sendfile_send(Host, Send, Orig, [{active,false}]). + +sendfile_send(Host, Send, Orig, SockOpts) -> + SFServer = spawn_link(?MODULE, sendfile_server, [self(), Orig]), receive {server, Port} -> - {ok, Sock} = gen_tcp:connect(Host, Port, - [binary,{packet,0}, - {active,false}]), + Opts = [binary,{packet,0}|SockOpts], + io:format("connect with opts = ~p\n", [Opts]), + {ok, Sock} = gen_tcp:connect(Host, Port, Opts), Data = case proplists:get_value(arity,erlang:fun_info(Send)) of 1 -> Send(Sock); diff --git a/lib/kernel/test/seq_trace_SUITE.erl b/lib/kernel/test/seq_trace_SUITE.erl index 3bbf62e832..663f910751 100644 --- a/lib/kernel/test/seq_trace_SUITE.erl +++ b/lib/kernel/test/seq_trace_SUITE.erl @@ -28,7 +28,8 @@ -export([token_set_get/1, tracer_set_get/1, print/1, send/1, distributed_send/1, recv/1, distributed_recv/1, trace_exit/1, distributed_exit/1, call/1, port/1, - match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1]). + match_set_seq_token/1, gc_seq_token/1, label_capability_mismatch/1, + send_literal/1]). %% internal exports -export([simple_tracer/2, one_time_receiver/0, one_time_receiver/1, @@ -47,7 +48,7 @@ suite() -> {timetrap,{minutes,1}}]. all() -> - [token_set_get, tracer_set_get, print, send, + [token_set_get, tracer_set_get, print, send, send_literal, distributed_send, recv, distributed_recv, trace_exit, distributed_exit, call, port, match_set_seq_token, gc_seq_token, label_capability_mismatch]. @@ -161,23 +162,51 @@ do_print(TsType) -> {0,{print,_,_,[],print3}, Ts1}] = stop_tracer(2), check_ts(TsType, Ts0), check_ts(TsType, Ts1). - + send(Config) when is_list(Config) -> lists:foreach(fun do_send/1, ?TIMESTAMP_MODES). do_send(TsType) -> + do_send(TsType, send). + +do_send(TsType, Msg) -> seq_trace:reset_trace(), start_tracer(), Receiver = spawn(?MODULE,one_time_receiver,[]), Label = make_ref(), seq_trace:set_token(label,Label), set_token_flags([send, TsType]), - Receiver ! send, + Receiver ! Msg, Self = self(), seq_trace:reset_trace(), - [{Label,{send,_,Self,Receiver,send}, Ts}] = stop_tracer(1), + [{Label,{send,_,Self,Receiver,Msg}, Ts}] = stop_tracer(1), check_ts(TsType, Ts). +%% This testcase tests that we do not segfault when we have a +%% literal as the message and the message is copied onto the +%% heap during a GC. +send_literal(Config) when is_list(Config) -> + lists:foreach(fun do_send_literal/1, + [atom, make_ref(), ets:new(hej,[]), 1 bsl 64, + "gurka", {tuple,test,with,#{}}, #{}]). + +do_send_literal(Msg) -> + N = 10000, + seq_trace:reset_trace(), + start_tracer(), + Label = make_ref(), + seq_trace:set_token(label,Label), + set_token_flags([send, 'receive', no_timestamp]), + Receiver = spawn_link(fun() -> receive ok -> ok end end), + [Receiver ! Msg || _ <- lists:seq(1, N)], + erlang:garbage_collect(Receiver), + [Receiver ! Msg || _ <- lists:seq(1, N)], + erlang:garbage_collect(Receiver), + Self = self(), + seq_trace:reset_trace(), + [{Label,{send,_,Self,Receiver,Msg}, Ts} | _] = stop_tracer(N), + check_ts(no_timestamp, Ts). + distributed_send(Config) when is_list(Config) -> lists:foreach(fun do_distributed_send/1, ?TIMESTAMP_MODES). diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index df95174c9f..4b43c6ae9d 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 6.1 +KERNEL_VSN = 6.2 diff --git a/lib/megaco/doc/src/megaco.xml b/lib/megaco/doc/src/megaco.xml index d4a7451bfc..c7bcdfcd6f 100644 --- a/lib/megaco/doc/src/megaco.xml +++ b/lib/megaco/doc/src/megaco.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco.xml</file> </header> - <module>megaco</module> + <module since="">megaco</module> <modulesummary>Main API of the Megaco application</modulesummary> <description> <p>Interface module for the Megaco application</p> @@ -135,7 +135,7 @@ megaco_incr_timer() = #megaco_incr_timer{} <funcs> <func> - <name>start() -> ok | {error, Reason}</name> + <name since="">start() -> ok | {error, Reason}</name> <fsummary>Starts the Megaco application</fsummary> <type> <v>Reason = term()</v> @@ -153,7 +153,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>stop() -> ok | {error, Reason}</name> + <name since="">stop() -> ok | {error, Reason}</name> <fsummary>Stops the Megaco application</fsummary> <type> <v>Reason = term()</v> @@ -166,7 +166,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>start_user(UserMid, Config) -> ok | {error, Reason}</name> + <name since="">start_user(UserMid, Config) -> ok | {error, Reason}</name> <fsummary>Initial configuration of a user</fsummary> <type> <v>UserMid = megaco_mid()</v> @@ -188,7 +188,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>stop_user(UserMid) -> ok | {error, Reason}</name> + <name since="">stop_user(UserMid) -> ok | {error, Reason}</name> <fsummary>Delete the configuration of a user</fsummary> <type> <v>UserMid = megaco_mid()</v> @@ -203,8 +203,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>user_info(UserMid) -> [{Item, Value}]</name> - <name>user_info(UserMid, Item) -> Value | exit(Reason)</name> + <name since="">user_info(UserMid) -> [{Item, Value}]</name> + <name since="">user_info(UserMid, Item) -> Value | exit(Reason)</name> <fsummary>Lookup user information</fsummary> <type> <v>Handle = user_info_handle()</v> @@ -703,7 +703,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>update_user_info(UserMid, Item, Value) -> ok | {error, Reason}</name> + <name since="">update_user_info(UserMid, Item, Value) -> ok | {error, Reason}</name> <fsummary>Update information about a user</fsummary> <type> <v>UserMid = megaco_mid() </v> @@ -721,8 +721,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>conn_info(ConnHandle) -> [{Item, Value}]</name> - <name>conn_info(ConnHandle, Item) -> Value | exit(Reason)</name> + <name since="">conn_info(ConnHandle) -> [{Item, Value}]</name> + <name since="">conn_info(ConnHandle, Item) -> Value | exit(Reason)</name> <fsummary>Lookup information about an active connection</fsummary> <type> <v>ConnHandle = #megaco_conn_handle{}</v> @@ -1222,7 +1222,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>update_conn_info(ConnHandle, Item, Value) -> ok | {error, Reason}</name> + <name since="">update_conn_info(ConnHandle, Item, Value) -> ok | {error, Reason}</name> <fsummary>Update information about an active connection</fsummary> <type> <v>ConnHandle = #megaco_conn_handle{}</v> @@ -1241,8 +1241,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>system_info() -> [{Item, Value}] | exit(Reason)</name> - <name>system_info(Item) -> Value | exit(Reason)</name> + <name since="">system_info() -> [{Item, Value}] | exit(Reason)</name> + <name since="">system_info(Item) -> Value | exit(Reason)</name> <fsummary>Lookup system information</fsummary> <type> <v>Item = system_info_item()</v> @@ -1289,7 +1289,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>info() -> Info</name> + <name since="">info() -> Info</name> <fsummary>All the information of the application</fsummary> <type> <v>Info = [{Key, Value}]</v> @@ -1311,8 +1311,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid) -> {ok, ConnHandle} | {error, Reason}</name> - <name>connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid, Extra) -> {ok, ConnHandle} | {error, Reason}</name> + <name since="">connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid) -> {ok, ConnHandle} | {error, Reason}</name> + <name since="">connect(ReceiveHandle, RemoteMid, SendHandle, ControlPid, Extra) -> {ok, ConnHandle} | {error, Reason}</name> <fsummary>Establish a "virtual" connection</fsummary> <type> <v>ReceiveHandle = #megaco_receive_handle{}</v> @@ -1436,7 +1436,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>disconnect(ConnHandle, DiscoReason) -> ok | {error, ErrReason}</name> + <name since="">disconnect(ConnHandle, DiscoReason) -> ok | {error, ErrReason}</name> <fsummary>Tear down a "virtual" connection</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -1454,7 +1454,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>call(ConnHandle, Actions, Options) -> {ProtocolVersion, UserReply}</name> + <name since="">call(ConnHandle, Actions, Options) -> {ProtocolVersion, UserReply}</name> <fsummary>Sends one or more transaction request(s) and waits for the reply</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -1545,7 +1545,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>cast(ConnHandle, Actions, Options) -> ok | {error, Reason}</name> + <name since="">cast(ConnHandle, Actions, Options) -> ok | {error, Reason}</name> <fsummary>Sends one or more transaction request(s) but does NOT wait for a reply</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -1582,7 +1582,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>encode_actions(ConnHandle, Actions, Options) -> {ok, BinOrBins} | {error, Reason}</name> + <name since="">encode_actions(ConnHandle, Actions, Options) -> {ok, BinOrBins} | {error, Reason}</name> <fsummary>Encode action requests for one or more transaction request(s)</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -1607,9 +1607,9 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>token_tag2string(Tag) -> Result</name> - <name>token_tag2string(Tag, EncoderMod) -> Result</name> - <name>token_tag2string(Tag, EncoderMod, Version) -> Result</name> + <name since="">token_tag2string(Tag) -> Result</name> + <name since="">token_tag2string(Tag, EncoderMod) -> Result</name> + <name since="">token_tag2string(Tag, EncoderMod, Version) -> Result</name> <fsummary>Convert a token tag to a string</fsummary> <type> <v>Tag = atom()</v> @@ -1635,7 +1635,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>cancel(ConnHandle, CancelReason) -> ok | {error, ErrReason}</name> + <name since="">cancel(ConnHandle, CancelReason) -> ok | {error, ErrReason}</name> <fsummary>Cancel all outstanding messages for this connection</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -1655,8 +1655,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name> - <name>process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name> + <name since="">process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name> + <name since="">process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name> <fsummary>Process a received message</fsummary> <type> <v>ReceiveHandle = #megaco_receive_handle{}</v> @@ -1755,8 +1755,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name> - <name>receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name> + <name since="">receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) -> ok</name> + <name since="">receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg, Extra) -> ok</name> <fsummary>Process a received message</fsummary> <type> <v>ReceiveHandle = #megaco_receive_handle{}</v> @@ -1783,7 +1783,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>parse_digit_map(DigitMapBody) -> {ok, ParsedDigitMap} | {error, Reason}</name> + <name since="">parse_digit_map(DigitMapBody) -> {ok, ParsedDigitMap} | {error, Reason}</name> <fsummary>Parses a digit map body</fsummary> <type> <v>DigitMapBody = string()</v> @@ -1802,8 +1802,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>eval_digit_map(DigitMap) -> {ok, MatchResult} | {error, Reason}</name> - <name>eval_digit_map(DigitMap, Timers) -> {ok, MatchResult} | {error, Reason}</name> + <name since="">eval_digit_map(DigitMap) -> {ok, MatchResult} | {error, Reason}</name> + <name since="">eval_digit_map(DigitMap, Timers) -> {ok, MatchResult} | {error, Reason}</name> <fsummary>Collect digit map letters according to the digit map</fsummary> <type> <v>DigitMap = #'DigitMapValue'{} | parsed_digit_map()</v> @@ -1839,7 +1839,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>report_digit_event(DigitMapEvalPid, Events) -> ok | {error, Reason}</name> + <name since="">report_digit_event(DigitMapEvalPid, Events) -> ok | {error, Reason}</name> <fsummary>Send one or more events to the event collector process</fsummary> <type> <v>DigitMapEvalPid = pid()</v> @@ -1866,7 +1866,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>test_digit_event(DigitMap, Events) -> {ok, Kind, Letters} | {error, Reason}</name> + <name since="">test_digit_event(DigitMap, Events) -> {ok, Kind, Letters} | {error, Reason}</name> <fsummary>Feed digit map collector with events and return the result</fsummary> <type> <v>DigitMap = #'DigitMapValue'{} | parsed_digit_map()</v> @@ -1900,7 +1900,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>encode_sdp(SDP) -> {ok, PP} | {error, Reason}</name> + <name since="">encode_sdp(SDP) -> {ok, PP} | {error, Reason}</name> <fsummary>Encode an SDP construct</fsummary> <type> <v>SDP = sdp_property_parm() | sdp_property_group() | sdp_property_groups() | asn1_NOVALUE</v> @@ -1929,7 +1929,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>decode_sdp(PP) -> {ok, SDP} | {error, Reason}</name> + <name since="">decode_sdp(PP) -> {ok, SDP} | {error, Reason}</name> <fsummary>Decode an property parameter construct</fsummary> <type> <v>PP = property_parm() | property_group() | property_groups() | asn1_NOVALUE</v> @@ -1969,7 +1969,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name> + <name since="">get_sdp_record_from_PropertGroup(Type, PG) -> [sdp()]</name> <fsummary>Get all sdp records of a certain type from a property group</fsummary> <type> <v>Type = v | c | m | o | a | b | t | r | z | k | s | i | u | e | p</v> @@ -1986,8 +1986,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>versions1() -> {ok, VersionInfo} | {error, Reason}</name> - <name>versions2() -> {ok, Info} | {error, Reason}</name> + <name since="">versions1() -> {ok, VersionInfo} | {error, Reason}</name> + <name since="">versions2() -> {ok, Info} | {error, Reason}</name> <fsummary>Retreive various system and application info</fsummary> <type> <v>VersionInfo = [version_info()]</v> @@ -2007,8 +2007,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>print_version_info() -> void()</name> - <name>print_version_info(VersionInfo) -> void()</name> + <name since="">print_version_info() -> void()</name> + <name since="">print_version_info(VersionInfo) -> void()</name> <fsummary>Formated print of result of the versions functions</fsummary> <type> <v>VersionInfo = [version_info()]</v> @@ -2029,7 +2029,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>enable_trace(Level, Destination) -> void()</name> + <name since="">enable_trace(Level, Destination) -> void()</name> <fsummary>Start megaco tracing</fsummary> <type> <v>Level = max | min | 0 <= integer() <= 100</v> @@ -2057,7 +2057,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>disable_trace() -> void()</name> + <name since="">disable_trace() -> void()</name> <fsummary>Stop megaco tracing</fsummary> <desc> <p>This function is used to stop megaco tracing.</p> @@ -2065,7 +2065,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </desc> </func> <func> - <name>set_trace(Level) -> void()</name> + <name since="">set_trace(Level) -> void()</name> <fsummary>Change megaco trace level</fsummary> <type> <v>Level = max | min | 0 <= integer() <= 100</v> @@ -2081,10 +2081,10 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name> - <name>get_stats(GlobalCounter) -> {ok, CounterStats} | {error, Reason}</name> - <name>get_stats(ConnHandle) -> {ok, ConnHandleStats} | {error, Reason}</name> - <name>get_stats(ConnHandle, Counter) -> {ok, integer()} | {error, Reason}</name> + <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name> + <name since="">get_stats(GlobalCounter) -> {ok, CounterStats} | {error, Reason}</name> + <name since="">get_stats(ConnHandle) -> {ok, ConnHandleStats} | {error, Reason}</name> + <name since="">get_stats(ConnHandle, Counter) -> {ok, integer()} | {error, Reason}</name> <fsummary></fsummary> <type> <v>TotalStats = [total_stats()]</v> @@ -2110,8 +2110,8 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>reset_stats() -> void()</name> - <name>reset_stats(ConnHandle) -> void()</name> + <name since="">reset_stats() -> void()</name> + <name since="">reset_stats(ConnHandle) -> void()</name> <fsummary></fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -2123,7 +2123,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>test_request(ConnHandle, Version, EncodingMod, EncodingConfig, Actions) -> {MegaMsg, EncodeRes}</name> + <name since="">test_request(ConnHandle, Version, EncodingMod, EncodingConfig, Actions) -> {MegaMsg, EncodeRes}</name> <fsummary>Tests if the Actions argument is correct</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -2150,7 +2150,7 @@ megaco_incr_timer() = #megaco_incr_timer{} </func> <func> - <name>test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> {MegaMsg, EncodeRes}</name> + <name since="">test_reply(ConnHandle, Version, EncodingMod, EncodingConfig, Reply) -> {MegaMsg, EncodeRes}</name> <fsummary>Tests if the Reply argument is correct</fsummary> <type> <v>ConnHandle = conn_handle()</v> diff --git a/lib/megaco/doc/src/megaco_codec_meas.xml b/lib/megaco/doc/src/megaco_codec_meas.xml index 13cc3eb834..5184fe392e 100644 --- a/lib/megaco/doc/src/megaco_codec_meas.xml +++ b/lib/megaco/doc/src/megaco_codec_meas.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_codec_meas.xml</file> </header> - <module>megaco_codec_meas</module> + <module since="">megaco_codec_meas</module> <modulesummary>This module implements a simple megaco codec measurement tool.</modulesummary> <description> <p>This module implements a simple megaco codec measurement tool.</p> @@ -43,8 +43,8 @@ <funcs> <func> - <name>start() -> void()</name> - <name>start(MessagePackage) -> void()</name> + <name since="">start() -> void()</name> + <name since="">start(MessagePackage) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackageRaw = message_package()</v> diff --git a/lib/megaco/doc/src/megaco_codec_mstone1.xml b/lib/megaco/doc/src/megaco_codec_mstone1.xml index 2ff959a648..507a790c71 100644 --- a/lib/megaco/doc/src/megaco_codec_mstone1.xml +++ b/lib/megaco/doc/src/megaco_codec_mstone1.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_codec_mstone1.xml</file> </header> - <module>megaco_codec_mstone1</module> + <module since="">megaco_codec_mstone1</module> <modulesummary>This module implements a simple megaco codec-based performance tool.</modulesummary> <description> <p>This module implements the <em>mstone1</em> tool, @@ -44,9 +44,9 @@ <funcs> <func> - <name>start() -> void()</name> - <name>start(MessagePackage) -> void()</name> - <name>start(MessagePackage, Factor) -> void()</name> + <name since="">start() -> void()</name> + <name since="">start(MessagePackage) -> void()</name> + <name since="">start(MessagePackage, Factor) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackage = message_package()</v> @@ -63,9 +63,9 @@ </func> <func> - <name>start_flex() -> void()</name> - <name>start_flex(MessagePackage) -> void()</name> - <name>start_flex(MessagePackage, Factor) -> void()</name> + <name since="">start_flex() -> void()</name> + <name since="">start_flex(MessagePackage) -> void()</name> + <name since="">start_flex(MessagePackage, Factor) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackage = message_package()</v> @@ -83,9 +83,9 @@ </func> <func> - <name>start_only_drv() -> void()</name> - <name>start_only_drv(MessagePackage) -> void()</name> - <name>start_only_drv(MessagePackage, Factor) -> void()</name> + <name since="">start_only_drv() -> void()</name> + <name since="">start_only_drv(MessagePackage) -> void()</name> + <name since="">start_only_drv(MessagePackage, Factor) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackage = message_package()</v> @@ -105,9 +105,9 @@ </func> <func> - <name>start_no_drv() -> void()</name> - <name>start_no_drv(MessagePackage) -> void()</name> - <name>start_no_drv(MessagePackage, Factor) -> void()</name> + <name since="">start_no_drv() -> void()</name> + <name since="">start_no_drv(MessagePackage) -> void()</name> + <name since="">start_no_drv(MessagePackage, Factor) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackage = message_package()</v> diff --git a/lib/megaco/doc/src/megaco_codec_mstone2.xml b/lib/megaco/doc/src/megaco_codec_mstone2.xml index 3da30d4f99..03990f5c3d 100644 --- a/lib/megaco/doc/src/megaco_codec_mstone2.xml +++ b/lib/megaco/doc/src/megaco_codec_mstone2.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_codec_mstone2.xml</file> </header> - <module>megaco_codec_mstone2</module> + <module since="">megaco_codec_mstone2</module> <modulesummary>This module implements a simple megaco codec-based performance tool.</modulesummary> <description> <p>This module implements the <em>mstone2</em> tool, @@ -44,8 +44,8 @@ <funcs> <func> - <name>start() -> void()</name> - <name>start(MessagePackage) -> void()</name> + <name since="">start() -> void()</name> + <name since="">start(MessagePackage) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackage = message_package()</v> diff --git a/lib/megaco/doc/src/megaco_codec_transform.xml b/lib/megaco/doc/src/megaco_codec_transform.xml index 26b83c3799..392868fdfa 100644 --- a/lib/megaco/doc/src/megaco_codec_transform.xml +++ b/lib/megaco/doc/src/megaco_codec_transform.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_codec_transform.xml</file> </header> - <module>megaco_codec_transform</module> + <module since="">megaco_codec_transform</module> <modulesummary>Megaco message transformation utility.</modulesummary> <description> @@ -45,8 +45,8 @@ <funcs> <func> - <name>export_messages() -> void()</name> - <name>export_messages(MessagePackage) -> void()</name> + <name since="">export_messages() -> void()</name> + <name since="">export_messages(MessagePackage) -> void()</name> <fsummary></fsummary> <type> <v>MessagePackage = atom()</v> diff --git a/lib/megaco/doc/src/megaco_edist_compress.xml b/lib/megaco/doc/src/megaco_edist_compress.xml index d5c7c7224d..16443e469c 100644 --- a/lib/megaco/doc/src/megaco_edist_compress.xml +++ b/lib/megaco/doc/src/megaco_edist_compress.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_edist_compress.xml</file> </header> - <module>megaco_edist_compress</module> + <module since="">megaco_edist_compress</module> <modulesummary>Megaco erlang dist compress behaviour.</modulesummary> <description> <p>The following functions should be exported from a @@ -40,7 +40,7 @@ </description> <funcs> <func> - <name>Module:encode(R, Version) -> T</name> + <name since="">Module:encode(R, Version) -> T</name> <fsummary>Encode (compress) a megaco component.</fsummary> <type> <v>R = megaco_message() | transaction() | action_reply() | action_request() | command_request()</v> @@ -53,7 +53,7 @@ </desc> </func> <func> - <name>Module:decode(T, Version) -> R</name> + <name since="">Module:decode(T, Version) -> R</name> <fsummary>Decode (decompress) a megaco component.</fsummary> <type> <v>T = term()</v> diff --git a/lib/megaco/doc/src/megaco_encoder.xml b/lib/megaco/doc/src/megaco_encoder.xml index 13c6ed324b..cc8270440b 100644 --- a/lib/megaco/doc/src/megaco_encoder.xml +++ b/lib/megaco/doc/src/megaco_encoder.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_encoder.xml</file> </header> - <module>megaco_encoder</module> + <module since="">megaco_encoder</module> <modulesummary>Megaco encoder behaviour.</modulesummary> <description> <p>The following functions should be exported from a @@ -64,7 +64,7 @@ action_reply() = #'ActionReply'{} <funcs> <func> - <name>Module:encode_message(EncodingConfig, Version, Message) -> {ok, Bin} | Error</name> + <name since="">Module:encode_message(EncodingConfig, Version, Message) -> {ok, Bin} | Error</name> <fsummary>Encode a megaco message.</fsummary> <type> <v>EncodingConfig = list()</v> @@ -81,7 +81,7 @@ action_reply() = #'ActionReply'{} </func> <func> - <name>Module:decode_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name> + <name since="">Module:decode_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name> <fsummary>Decode a megaco message.</fsummary> <type> <v>EncodingConfig = list()</v> @@ -104,7 +104,7 @@ action_reply() = #'ActionReply'{} </func> <func> - <name>Module:decode_mini_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name> + <name since="">Module:decode_mini_message(EncodingConfig, Version, Bin) -> {ok, Message} | Error</name> <fsummary>Perform a minimal decode of a megaco message.</fsummary> <type> <v>EncodingConfig = list()</v> @@ -129,7 +129,7 @@ action_reply() = #'ActionReply'{} </func> <func> - <name>Module:encode_transaction(EncodingConfig, Version, Transaction) -> OK | Error</name> + <name since="">Module:encode_transaction(EncodingConfig, Version, Transaction) -> OK | Error</name> <fsummary>Encode a megaco transaction.</fsummary> <type> <v>EncodingConfig = list()</v> @@ -155,7 +155,7 @@ action_reply() = #'ActionReply'{} </func> <func> - <name>Module:encode_action_requests(EncodingConfig, Version, ARs) -> OK | Error</name> + <name since="">Module:encode_action_requests(EncodingConfig, Version, ARs) -> OK | Error</name> <fsummary>Encode megaco action requests.</fsummary> <type> <v>EncodingConfig = list()</v> @@ -181,7 +181,7 @@ action_reply() = #'ActionReply'{} </func> <func> - <name>Module:encode_action_reply(EncodingConfig, Version, AR) -> OK | Error</name> + <name since="">Module:encode_action_reply(EncodingConfig, Version, AR) -> OK | Error</name> <fsummary>Encode a megaco action reply.</fsummary> <type> <v>EncodingConfig = list()</v> diff --git a/lib/megaco/doc/src/megaco_flex_scanner.xml b/lib/megaco/doc/src/megaco_flex_scanner.xml index 0856f3f429..121a7fbcff 100644 --- a/lib/megaco/doc/src/megaco_flex_scanner.xml +++ b/lib/megaco/doc/src/megaco_flex_scanner.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_flex_scanner.xml</file> </header> - <module>megaco_flex_scanner</module> + <module since="">megaco_flex_scanner</module> <modulesummary>Interface module to the flex scanner linked in driver.</modulesummary> <description> <p>This module contains the public interface to the flex scanner @@ -72,7 +72,7 @@ megaco_version() = integer() >= 1 <funcs> <func> - <name>start() -> {ok, PortOrPorts} | {error, Reason}</name> + <name since="">start() -> {ok, PortOrPorts} | {error, Reason}</name> <fsummary></fsummary> <type> <v>PortOrPorts = megaco_ports()</v> @@ -94,7 +94,7 @@ megaco_version() = integer() >= 1 </func> <func> - <name>stop(PortOrPorts) -> stopped</name> + <name since="">stop(PortOrPorts) -> stopped</name> <fsummary></fsummary> <type> <v>PortOrPorts = megaco_ports()</v> @@ -108,7 +108,7 @@ megaco_version() = integer() >= 1 </func> <func> - <name>is_reentrant_enabled() -> Boolean</name> + <name since="">is_reentrant_enabled() -> Boolean</name> <fsummary></fsummary> <type> <v>Boolean = boolean()</v> @@ -121,7 +121,7 @@ megaco_version() = integer() >= 1 </func> <func> - <name>is_scanner_port(Port, PortOrPorts) -> Boolean</name> + <name since="">is_scanner_port(Port, PortOrPorts) -> Boolean</name> <fsummary></fsummary> <type> <v>Port = port()</v> @@ -137,7 +137,7 @@ megaco_version() = integer() >= 1 </func> <func> - <name>scan(Binary, PortOrPorts) -> {ok, Tokens, Version, LatestLine} | {error, Reason, LatestLine} </name> + <name since="">scan(Binary, PortOrPorts) -> {ok, Tokens, Version, LatestLine} | {error, Reason, LatestLine} </name> <fsummary></fsummary> <type> <v>Binary = binary()</v> diff --git a/lib/megaco/doc/src/megaco_tcp.xml b/lib/megaco/doc/src/megaco_tcp.xml index 77aee32f6c..63713b2c56 100644 --- a/lib/megaco/doc/src/megaco_tcp.xml +++ b/lib/megaco/doc/src/megaco_tcp.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_tcp.xml</file> </header> - <module>megaco_tcp</module> + <module since="">megaco_tcp</module> <modulesummary>Interface module to TPKT transport protocol for Megaco/H.248.</modulesummary> <description> <p>This module contains the public interface to the TPKT (TCP/IP) version @@ -40,7 +40,7 @@ </description> <funcs> <func> - <name>start_transport() -> {ok, TransportRef}</name> + <name since="">start_transport() -> {ok, TransportRef}</name> <fsummary></fsummary> <type> <v>TransportRef = pid()</v> @@ -51,7 +51,7 @@ </desc> </func> <func> - <name>listen(TransportRef, ListenPortSpecList) -> ok</name> + <name since="">listen(TransportRef, ListenPortSpecList) -> ok</name> <fsummary></fsummary> <type> <v>TransportRef = pid() | regname()</v> @@ -65,7 +65,7 @@ </desc> </func> <func> - <name>connect(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name> + <name since="">connect(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name> <fsummary></fsummary> <type> <v>TransportRef = pid() | regname()</v> @@ -86,7 +86,7 @@ </desc> </func> <func> - <name>close(Handle) -> ok</name> + <name since="">close(Handle) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -96,7 +96,7 @@ </desc> </func> <func> - <name>socket(Handle) -> Socket</name> + <name since="">socket(Handle) -> Socket</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -109,7 +109,7 @@ </desc> </func> <func> - <name>send_message(Handle, Message) -> ok</name> + <name since="">send_message(Handle, Message) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -120,7 +120,7 @@ </desc> </func> <func> - <name>block(Handle) -> ok</name> + <name since="">block(Handle) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -130,7 +130,7 @@ </desc> </func> <func> - <name>unblock(Handle) -> ok</name> + <name since="">unblock(Handle) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -141,7 +141,7 @@ </desc> </func> <func> - <name>upgrade_receive_handle(ControlPid) -> ok</name> + <name since="">upgrade_receive_handle(ControlPid) -> ok</name> <fsummary></fsummary> <type> <v>ControlPid = pid()</v> @@ -153,9 +153,9 @@ </desc> </func> <func> - <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name> - <name>get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name> - <name>get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name> + <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name> + <name since="">get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name> + <name since="">get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name> <fsummary></fsummary> <type> <v>TotalStats = [send_handle_stats()]</v> @@ -173,8 +173,8 @@ </desc> </func> <func> - <name>reset_stats() -> void()</name> - <name>reset_stats(SendHandle) -> void()</name> + <name since="">reset_stats() -> void()</name> + <name since="">reset_stats(SendHandle) -> void()</name> <fsummary></fsummary> <type> <v>SendHandle = send_handle()</v> diff --git a/lib/megaco/doc/src/megaco_transport.xml b/lib/megaco/doc/src/megaco_transport.xml index 3002e9b74e..ba8c794750 100644 --- a/lib/megaco/doc/src/megaco_transport.xml +++ b/lib/megaco/doc/src/megaco_transport.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_transport.xml</file> </header> - <module>megaco_transport</module> + <module since="">megaco_transport</module> <modulesummary>Megaco transport behaviour.</modulesummary> <description> <p>The following functions should be exported from a @@ -54,8 +54,8 @@ </description> <funcs> <func> - <name>Module:send_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name> - <name>Module:send_message(Handle, Msg, Resend) -> ok | {cancel, Reason} | Error</name> + <name since="">Module:send_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name> + <name since="">Module:send_message(Handle, Msg, Resend) -> ok | {cancel, Reason} | Error</name> <fsummary>Send a megaco message.</fsummary> <type> <v>Handle = term()</v> @@ -99,7 +99,7 @@ </func> <func> - <name>Module:resend_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name> + <name since="">Module:resend_message(Handle, Msg) -> ok | {cancel, Reason} | Error</name> <fsummary>Re-send a megaco message.</fsummary> <type> <v>Handle = term()</v> diff --git a/lib/megaco/doc/src/megaco_udp.xml b/lib/megaco/doc/src/megaco_udp.xml index b2559c77d5..3d776c19b6 100644 --- a/lib/megaco/doc/src/megaco_udp.xml +++ b/lib/megaco/doc/src/megaco_udp.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_udp.xml</file> </header> - <module>megaco_udp</module> + <module since="">megaco_udp</module> <modulesummary>Interface module to UDP transport protocol for Megaco/H.248.</modulesummary> <description> <p>This module contains the public interface to the UDP/IP version @@ -40,7 +40,7 @@ </description> <funcs> <func> - <name>start_transport() -> {ok, TransportRef}</name> + <name since="">start_transport() -> {ok, TransportRef}</name> <fsummary></fsummary> <type> <v>TransportRef = pid()</v> @@ -51,7 +51,7 @@ </desc> </func> <func> - <name>open(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name> + <name since="">open(TransportRef, OptionList) -> {ok, Handle, ControlPid} | {error, Reason}</name> <fsummary></fsummary> <type> <v>TransportRef = pid() | regname()</v> @@ -73,7 +73,7 @@ </desc> </func> <func> - <name>close(Handle, Msg) -> ok</name> + <name since="">close(Handle, Msg) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -84,7 +84,7 @@ </desc> </func> <func> - <name>socket(Handle) -> Socket</name> + <name since="">socket(Handle) -> Socket</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -97,7 +97,7 @@ </desc> </func> <func> - <name>create_send_handle(Handle, Host, Port) -> send_handle()</name> + <name since="">create_send_handle(Handle, Host, Port) -> send_handle()</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -110,7 +110,7 @@ </desc> </func> <func> - <name>send_message(SendHandle, Msg) -> ok</name> + <name since="">send_message(SendHandle, Msg) -> ok</name> <fsummary></fsummary> <type> <v>SendHandle = send_handle()</v> @@ -125,7 +125,7 @@ </desc> </func> <func> - <name>block(Handle) -> ok</name> + <name since="">block(Handle) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -135,7 +135,7 @@ </desc> </func> <func> - <name>unblock(Handle) -> ok</name> + <name since="">unblock(Handle) -> ok</name> <fsummary></fsummary> <type> <v>Handle = socket_handle()</v> @@ -146,7 +146,7 @@ </desc> </func> <func> - <name>upgrade_receive_handle(ControlPid, NewHandle) -> ok</name> + <name since="">upgrade_receive_handle(ControlPid, NewHandle) -> ok</name> <fsummary></fsummary> <type> <v>ControlPid = pid()</v> @@ -160,9 +160,9 @@ </desc> </func> <func> - <name>get_stats() -> {ok, TotalStats} | {error, Reason}</name> - <name>get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name> - <name>get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name> + <name since="">get_stats() -> {ok, TotalStats} | {error, Reason}</name> + <name since="">get_stats(SendHandle) -> {ok, SendHandleStats} | {error, Reason}</name> + <name since="">get_stats(SendHandle, Counter) -> {ok, CounterStats} | {error, Reason}</name> <fsummary></fsummary> <type> <v>TotalStats = [total_stats()]</v> @@ -180,8 +180,8 @@ </desc> </func> <func> - <name>reset_stats() -> void()</name> - <name>reset_stats(SendHandle) -> void()</name> + <name since="">reset_stats() -> void()</name> + <name since="">reset_stats(SendHandle) -> void()</name> <fsummary></fsummary> <type> <v>SendHandle = send_handle()</v> diff --git a/lib/megaco/doc/src/megaco_user.xml b/lib/megaco/doc/src/megaco_user.xml index 067be15fe0..198f2aa24c 100644 --- a/lib/megaco/doc/src/megaco_user.xml +++ b/lib/megaco/doc/src/megaco_user.xml @@ -32,7 +32,7 @@ <rev>%VSN%</rev> <file>megaco_user.xml</file> </header> - <module>megaco_user</module> + <module since="">megaco_user</module> <modulesummary>Callback module for users of the Megaco application</modulesummary> <description> <p>This module defines the callback behaviour of Megaco users. A @@ -164,8 +164,8 @@ protocol_version() = integer() ]]></code> <funcs> <func> - <name>handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name> - <name>handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name> + <name since="">handle_connect(ConnHandle, ProtocolVersion) -> ok | error | {error,ErrorDescr}</name> + <name since="">handle_connect(ConnHandle, ProtocolVersion, Extra]) -> ok | error | {error,ErrorDescr}</name> <fsummary>Invoked when a new connection is established</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -202,7 +202,7 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> ok</name> + <name since="">handle_disconnect(ConnHandle, ProtocolVersion, Reason) -> ok</name> <fsummary>Invoked when a connection is teared down</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -220,8 +220,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name> - <name>handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name> + <name since="">handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name> + <name since="">handle_syntax_error(ReceiveHandle, ProtocolVersion, DefaultED, Extra) -> reply | {reply, ED} | no_reply | {no_reply, ED} </name> <fsummary>Invoked when a received message had syntax errors</fsummary> <type> <v>ReceiveHandle = receive_handle()</v> @@ -258,8 +258,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> ok</name> - <name>handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> ok</name> + <name since="">handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr) -> ok</name> + <name since="">handle_message_error(ConnHandle, ProtocolVersion, ErrorDescr, Extra) -> ok</name> <fsummary>Invoked when a received message just contains an error</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -291,8 +291,8 @@ protocol_version() = integer() ]]></code> <!-- <func> - <name>handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError) -> ok</name> - <name>handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError, Extra) -> ok</name> + <name since="">handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError) -> ok</name> + <name since="">handle_segment_error(ConnHandle, ProtocolVersion, TransId, SegmentError, Extra) -> ok</name> <fsummary>Invoked when a segment error has been detected</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -316,8 +316,8 @@ protocol_version() = integer() ]]></code> --> <func> - <name>handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> pending() | reply() | ignore_trans_request</name> - <name>handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra) -> pending() | reply() | ignore_trans_request</name> + <name since="">handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests) -> pending() | reply() | ignore_trans_request</name> + <name since="">handle_trans_request(ConnHandle, ProtocolVersion, ActionRequests, Extra) -> pending() | reply() | ignore_trans_request</name> <fsummary>Invoked for each transaction request</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -420,8 +420,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> reply()</name> - <name>handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> reply()</name> + <name since="">handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData) -> reply()</name> + <name since="">handle_trans_long_request(ConnHandle, ProtocolVersion, ReqData, Extra) -> reply()</name> <fsummary>Optionally invoked for a time consuming transaction request</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -460,8 +460,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData) -> ok</name> - <name>handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData, Extra) -> ok</name> + <name since="">handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData) -> ok</name> + <name since="">handle_trans_reply(ConnHandle, ProtocolVersion, UserReply, ReplyData, Extra) -> ok</name> <fsummary>Optionally invoked for a transaction reply</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -593,8 +593,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> ok</name> - <name>handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Extra) -> ok</name> + <name since="">handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData) -> ok</name> + <name since="">handle_trans_ack(ConnHandle, ProtocolVersion, AckStatus, AckData, Extra) -> ok</name> <fsummary>Optionally invoked for a transaction acknowledgement</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -658,8 +658,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> ok</name> - <name>handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> ok</name> + <name since="">handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans) -> ok</name> + <name since="">handle_unexpected_trans(ConnHandle, ProtocolVersion, Trans, Extra) -> ok</name> <fsummary>Invoked when an unexpected message is received</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -685,8 +685,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid) -> ok</name> - <name>handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid, Extra) -> ok</name> + <name since="">handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid) -> ok</name> + <name since="">handle_trans_request_abort(ConnHandle, ProtocolVersion, TransNo, Pid, Extra) -> ok</name> <fsummary>Invoked when an transaction request has been aborted</fsummary> <type> <v>ConnHandle = conn_handle()</v> @@ -710,8 +710,8 @@ protocol_version() = integer() ]]></code> </func> <func> - <name>handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl) -> ok</name> - <name>handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl, Extra) -> ok</name> + <name since="">handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl) -> ok</name> + <name since="">handle_segment_reply(ConnHandle, ProtocolVersion, TransNo, SegNo, SegCompl, Extra) -> ok</name> <fsummary>Segment Reply Indication</fsummary> <type> <v>ConnHandle = conn_handle()</v> diff --git a/lib/mnesia/doc/src/mnesia.xml b/lib/mnesia/doc/src/mnesia.xml index 621b6047ee..11b0b8e987 100644 --- a/lib/mnesia/doc/src/mnesia.xml +++ b/lib/mnesia/doc/src/mnesia.xml @@ -32,7 +32,7 @@ <rev></rev> <file></file> </header> - <module>mnesia</module> + <module since="">mnesia</module> <modulesummary>A distributed telecommunications DBMS</modulesummary> <description> @@ -183,7 +183,7 @@ <funcs> <func> - <name>abort(Reason) -> transaction abort</name> + <name since="">abort(Reason) -> transaction abort</name> <fsummary>Terminates the current transaction.</fsummary> <desc> <p>Makes the transaction silently @@ -195,7 +195,7 @@ </desc> </func> <func> - <name>activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name> + <name since="">activate_checkpoint(Args) -> {ok,Name,Nodes} | {error,Reason}</name> <fsummary>Activates a checkpoint.</fsummary> <desc> <marker id="activate_checkpoint"></marker> @@ -259,7 +259,7 @@ </desc> </func> <func> - <name>activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name> + <name since="">activity(AccessContext, Fun [, Args]) -> ResultOfFun | exit(Reason)</name> <fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary> <desc> <marker id="activity_2_3"></marker> @@ -271,7 +271,7 @@ </desc> </func> <func> - <name>activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name> + <name since="">activity(AccessContext, Fun, Args, AccessMod) -> ResultOfFun | exit(Reason)</name> <fsummary>Executes <c>Fun</c> in <c>AccessContext</c>.</fsummary> <desc> <marker id="activity_4"></marker> @@ -403,7 +403,7 @@ </desc> </func> <func> - <name>add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name> + <name since="">add_table_copy(Tab, Node, Type) -> {aborted, R} | {atomic, ok}</name> <fsummary>Copies a table to a remote node.</fsummary> <desc> <marker id="add_table_copy"></marker> @@ -420,7 +420,7 @@ mnesia:add_table_copy(person, Node, disc_copies)</code> </desc> </func> <func> - <name>add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name> + <name since="">add_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name> <fsummary>Creates an index for a table.</fsummary> <desc> <marker id="add_table_index"></marker> @@ -441,7 +441,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>all_keys(Tab) -> KeyList | transaction abort</name> + <name since="">all_keys(Tab) -> KeyList | transaction abort</name> <fsummary>Returns all keys in a table.</fsummary> <desc> <marker id="all_keys"></marker> @@ -453,7 +453,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name> + <name since="">async_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name> <fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary> <desc> <marker id="async_dirty"></marker> @@ -493,7 +493,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name> + <name since="">backup(Opaque [, BackupMod]) -> ok | {error,Reason}</name> <fsummary>Backs up all tables in the database.</fsummary> <desc> <marker id="backup"></marker> @@ -505,7 +505,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name> + <name since="">backup_checkpoint(Name, Opaque [, BackupMod]) -> ok | {error,Reason}</name> <fsummary>Backs up all tables in a checkpoint.</fsummary> <desc> <marker id="backup_checkpoint"></marker> @@ -520,7 +520,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name> + <name since="">change_config(Config, Value) -> {error, Reason} | {ok, ReturnValue}</name> <fsummary>Changes a configuration parameter.</fsummary> <desc> <marker id="change_config"></marker> @@ -554,7 +554,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name> + <name since="">change_table_access_mode(Tab, AccessMode) -> {aborted, R} | {atomic, ok}</name> <fsummary>Changes the access mode for the table.</fsummary> <desc> <marker id="change_table_access_mode"></marker> @@ -568,7 +568,7 @@ mnesia:add_table_index(person, age)</code> </desc> </func> <func> - <name>change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name> + <name since="">change_table_copy_type(Tab, Node, To) -> {aborted, R} | {atomic, ok}</name> <fsummary>Changes the storage type of a table.</fsummary> <desc> <marker id="change_table_copy_type"></marker> @@ -585,7 +585,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name>change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name> + <name since="">change_table_load_order(Tab, LoadOrder) -> {aborted, R} | {atomic, ok}</name> <fsummary>Changes the load order priority for the table.</fsummary> <desc> <marker id="change_table_load_order"></marker> @@ -595,7 +595,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name>change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name> + <name since="OTP R14B03">change_table_majority(Tab, Majority) -> {aborted, R} | {atomic, ok}</name> <fsummary>Changes the majority check setting for the table.</fsummary> <desc> <p><c>Majority</c> must be a boolean. Default is <c>false</c>. @@ -607,7 +607,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name>clear_table(Tab) -> {aborted, R} | {atomic, ok}</name> + <name since="">clear_table(Tab) -> {aborted, R} | {atomic, ok}</name> <fsummary>Deletes all entries in a table.</fsummary> <desc> <marker id="clear_table"></marker> @@ -615,7 +615,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name>create_schema(DiscNodes) -> ok | {error,Reason}</name> + <name since="">create_schema(DiscNodes) -> ok | {error,Reason}</name> <fsummary>Creates a new schema on the specified nodes.</fsummary> <desc> <marker id="create_schema"></marker> @@ -637,7 +637,7 @@ mnesia:change_table_copy_type(person, node(), disc_copies)</code> </desc> </func> <func> - <name>create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name> + <name since="">create_table(Name, TabDef) -> {atomic, ok} | {aborted, Reason}</name> <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> @@ -799,7 +799,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>deactivate_checkpoint(Name) -> ok | {error, Reason}</name> + <name since="">deactivate_checkpoint(Name) -> ok | {error, Reason}</name> <fsummary>Deactivates a checkpoint.</fsummary> <desc> <marker id="deactivate_checkpoint"></marker> @@ -811,7 +811,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name> + <name since="">del_table_copy(Tab, Node) -> {aborted, R} | {atomic, ok}</name> <fsummary>Deletes the replica of table <c>Tab</c> at node <c>Node</c>.</fsummary> <desc> <marker id="del_table_copy"></marker> @@ -825,7 +825,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name> + <name since="">del_table_index(Tab, AttrName) -> {aborted, R} | {atomic, ok}</name> <fsummary>Deletes an index in a table.</fsummary> <desc> <marker id="del_table_index"></marker> @@ -834,7 +834,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>delete({Tab, Key}) -> transaction abort | ok</name> + <name since="">delete({Tab, Key}) -> transaction abort | ok</name> <fsummary>Deletes all records in table <c>Tab</c> with the key <c>Key</c>.</fsummary> <desc> <marker id="delete_2"></marker> @@ -842,7 +842,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>delete(Tab, Key, LockKind) -> transaction abort | ok</name> + <name since="">delete(Tab, Key, LockKind) -> transaction abort | ok</name> <fsummary>Deletes all records in table <c>Tab</c>with the key <c>Key</c>.</fsummary> <desc> <marker id="delete_3"></marker> @@ -857,7 +857,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>delete_object(Record) -> transaction abort | ok</name> + <name since="">delete_object(Record) -> transaction abort | ok</name> <fsummary>Delete a record.</fsummary> <desc> <marker id="delete_object_1"></marker> @@ -866,7 +866,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>delete_object(Tab, Record, LockKind) -> transaction abort | ok</name> + <name since="">delete_object(Tab, Record, LockKind) -> transaction abort | ok</name> <fsummary>Deletes a record.</fsummary> <desc> <marker id="delete_object_3"></marker> @@ -883,7 +883,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>delete_schema(DiscNodes) -> ok | {error,Reason}</name> + <name since="">delete_schema(DiscNodes) -> ok | {error,Reason}</name> <fsummary>Deletes the schema on the given nodes.</fsummary> <desc> <marker id="delete_schema"></marker> @@ -904,7 +904,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name> + <name since="">delete_table(Tab) -> {aborted, Reason} | {atomic, ok}</name> <fsummary>Deletes permanently all replicas of table <c>Tab</c>.</fsummary> <desc> <marker id="delete_table"></marker> @@ -912,7 +912,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name> + <name since="">dirty_all_keys(Tab) -> KeyList | exit({aborted, Reason})</name> <fsummary>Dirty search for all record keys in table.</fsummary> <desc> <marker id="delete_all_keys"></marker> @@ -920,7 +920,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name> + <name since="">dirty_delete({Tab, Key}) -> ok | exit({aborted, Reason})</name> <fsummary>Dirty delete of a record.</fsummary> <desc> <marker id="dirty_delete"></marker> @@ -928,14 +928,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name> + <name since="">dirty_delete(Tab, Key) -> ok | exit({aborted, Reason})</name> <fsummary>Dirty delete of a record.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:delete/3</c>.</p> </desc> </func> <func> - <name>dirty_delete_object(Record)</name> + <name since="">dirty_delete_object(Record)</name> <fsummary>Dirty delete of a record.</fsummary> <desc> <marker id="dirty_delete_object_1"></marker> @@ -944,14 +944,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_delete_object(Tab, Record)</name> + <name since="">dirty_delete_object(Tab, Record)</name> <fsummary>Dirty delete of a record.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:delete_object/3</c>.</p> </desc> </func> <func> - <name>dirty_first(Tab) -> Key | exit({aborted, Reason})</name> + <name since="">dirty_first(Tab) -> Key | exit({aborted, Reason})</name> <fsummary>Returns the key for the first record in a table.</fsummary> <desc> <marker id="dirty_first"></marker> @@ -967,7 +967,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_index_match_object(Pattern, Pos)</name> + <name since="">dirty_index_match_object(Pattern, Pos)</name> <fsummary>Dirty pattern match using index.</fsummary> <desc> <marker id="dirty_index_match_object_2"></marker> @@ -977,7 +977,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_index_match_object(Tab, Pattern, Pos)</name> + <name since="">dirty_index_match_object(Tab, Pattern, Pos)</name> <fsummary>Dirty pattern match using index.</fsummary> <desc> <p>Dirty equivalent of the function @@ -985,7 +985,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_index_read(Tab, SecondaryKey, Pos)</name> + <name since="">dirty_index_read(Tab, SecondaryKey, Pos)</name> <fsummary>Dirty read using index.</fsummary> <desc> <marker id="dirty_index_read"></marker> @@ -994,7 +994,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_last(Tab) -> Key | exit({aborted, Reason})</name> + <name since="">dirty_last(Tab) -> Key | exit({aborted, Reason})</name> <fsummary>Returns the key for the last record in a table.</fsummary> <desc> <marker id="dirty_last"></marker> @@ -1006,7 +1006,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name> + <name since="">dirty_match_object(Pattern) -> RecordList | exit({aborted, Reason})</name> <fsummary>Dirty pattern match pattern.</fsummary> <desc> <marker id="dirty_match_object_1"></marker> @@ -1015,7 +1015,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name> + <name since="">dirty_match_object(Tab, Pattern) -> RecordList | exit({aborted, Reason})</name> <fsummary>Dirty pattern match pattern.</fsummary> <desc> <p>Dirty equivalent of the function @@ -1023,7 +1023,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name> + <name since="">dirty_next(Tab, Key) -> Key | exit({aborted, Reason})</name> <fsummary>Return the next key in a table.</fsummary> <desc> <marker id="dirty_next"></marker> @@ -1038,7 +1038,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name> + <name since="">dirty_prev(Tab, Key) -> Key | exit({aborted, Reason})</name> <fsummary>Returns the previous key in a table.</fsummary> <desc> <marker id="dirty_prev"></marker> @@ -1050,7 +1050,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name> + <name since="">dirty_read({Tab, Key}) -> ValueList | exit({aborted, Reason}</name> <fsummary>Dirty read of records.</fsummary> <desc> <marker id="dirty_read"></marker> @@ -1058,14 +1058,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name> + <name since="">dirty_read(Tab, Key) -> ValueList | exit({aborted, Reason}</name> <fsummary>Dirty read of records.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:read/3</c>.</p> </desc> </func> <func> - <name>dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name> + <name since="">dirty_select(Tab, MatchSpec) -> ValueList | exit({aborted, Reason}</name> <fsummary>Dirty matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary> <desc> <marker id="dirty_select"></marker> @@ -1073,7 +1073,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name> + <name since="">dirty_slot(Tab, Slot) -> RecordList | exit({aborted, Reason})</name> <fsummary>Returns the list of records that are associated with <c>Slot</c> in a table.</fsummary> <desc> <marker id="dirty_slot"></marker> @@ -1089,7 +1089,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name> + <name since="">dirty_update_counter({Tab, Key}, Incr) -> NewVal | exit({aborted, Reason})</name> <fsummary>Dirty update of a counter record.</fsummary> <desc> <marker id="dirty_update_counter"></marker> @@ -1097,7 +1097,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name> + <name since="">dirty_update_counter(Tab, Key, Incr) -> NewVal | exit({aborted, Reason})</name> <fsummary>Dirty update of a counter record.</fsummary> <desc> <p>Mnesia has no special counter records. However, @@ -1126,7 +1126,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_write(Record) -> ok | exit({aborted, Reason})</name> + <name since="">dirty_write(Record) -> ok | exit({aborted, Reason})</name> <fsummary>Dirty write of a record.</fsummary> <desc> <marker id="dirty_write_1"></marker> @@ -1135,14 +1135,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name> + <name since="">dirty_write(Tab, Record) -> ok | exit({aborted, Reason})</name> <fsummary>Dirty write of a record.</fsummary> <desc> <p>Dirty equivalent of the function <c>mnesia:write/3</c>.</p> </desc> </func> <func> - <name>dump_log() -> dumped</name> + <name since="">dump_log() -> dumped</name> <fsummary>Performs a user-initiated dump of the local log file.</fsummary> <desc> <marker id="dump_log"></marker> @@ -1156,7 +1156,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name> + <name since="">dump_tables(TabList) -> {atomic, ok} | {aborted, Reason}</name> <fsummary>Dumps all RAM tables to disc.</fsummary> <desc> <marker id="dump_tables"></marker> @@ -1168,7 +1168,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>dump_to_textfile(Filename)</name> + <name since="">dump_to_textfile(Filename)</name> <fsummary>Dumps local tables into a text file.</fsummary> <desc> <marker id="dump_to_textfile"></marker> @@ -1181,7 +1181,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>error_description(Error) -> String</name> + <name since="">error_description(Error) -> String</name> <fsummary>Returns a string describing a particular Mnesia error.</fsummary> <desc> <marker id="error_description"></marker> @@ -1259,7 +1259,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name> + <name since="">ets(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name> <fsummary>Calls the <c>Fun</c> in a raw context that is not protected by a transaction.</fsummary> <desc> <marker id="ets"></marker> @@ -1278,7 +1278,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>first(Tab) -> Key | transaction abort</name> + <name since="">first(Tab) -> Key | transaction abort</name> <fsummary>Returns the key for the first record in a table.</fsummary> <desc> <marker id="first"></marker> @@ -1293,7 +1293,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>foldl(Function, Acc, Table) -> NewAcc | transaction abort</name> + <name since="">foldl(Function, Acc, Table) -> NewAcc | transaction abort</name> <fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary> <desc> <marker id="foldl"></marker> @@ -1306,7 +1306,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>foldr(Function, Acc, Table) -> NewAcc | transaction abort</name> + <name since="">foldr(Function, Acc, Table) -> NewAcc | transaction abort</name> <fsummary>Calls <c>Function</c> for each record in <c>Table</c>.</fsummary> <desc> <marker id="foldr"></marker> @@ -1317,7 +1317,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>force_load_table(Tab) -> yes | ErrorDescription</name> + <name since="">force_load_table(Tab) -> yes | ErrorDescription</name> <fsummary>Forces a table to be loaded into the system.</fsummary> <desc> <marker id="force_load_table"></marker> @@ -1335,7 +1335,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>index_match_object(Pattern, Pos) -> transaction abort | ObjList</name> + <name since="">index_match_object(Pattern, Pos) -> transaction abort | ObjList</name> <fsummary>Matches records and uses index information.</fsummary> <desc> <marker id="index_match_object_2"></marker> @@ -1345,7 +1345,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name> + <name since="">index_match_object(Tab, Pattern, Pos, LockKind) -> transaction abort | ObjList</name> <fsummary>Matches records and uses index information.</fsummary> <desc> <marker id="index_match_object_4"></marker> @@ -1377,7 +1377,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name> + <name since="">index_read(Tab, SecondaryKey, Pos) -> transaction abort | RecordList</name> <fsummary>Reads records through index table.</fsummary> <desc> <marker id="index_read"></marker> @@ -1397,7 +1397,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>info() -> ok</name> + <name since="">info() -> ok</name> <fsummary>Prints system information on the terminal.</fsummary> <desc> <marker id="info"></marker> @@ -1408,7 +1408,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>install_fallback(Opaque) -> ok | {error,Reason}</name> + <name since="">install_fallback(Opaque) -> ok | {error,Reason}</name> <fsummary>Installs a backup as fallback.</fsummary> <desc> <marker id="install_fallback_1"></marker> @@ -1417,7 +1417,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name> + <name since="">install_fallback(Opaque), BackupMod) -> ok | {error,Reason}</name> <fsummary>Installs a backup as fallback.</fsummary> <desc> <p>Calls <c>mnesia:install_fallback(Opaque, Args)</c>, where @@ -1425,7 +1425,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>install_fallback(Opaque, Args) -> ok | {error,Reason}</name> + <name since="">install_fallback(Opaque, Args) -> ok | {error,Reason}</name> <fsummary>Installs a backup as fallback.</fsummary> <desc> <p>Installs a backup as fallback. The fallback is used to @@ -1483,7 +1483,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>is_transaction() -> boolean</name> + <name since="">is_transaction() -> boolean</name> <fsummary>Checks if code is running in a transaction.</fsummary> <desc> <marker id="is_transaction"></marker> @@ -1492,7 +1492,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>last(Tab) -> Key | transaction abort</name> + <name since="">last(Tab) -> Key | transaction abort</name> <fsummary>Returns the key for the last record in a table.</fsummary> <desc> <p>Works exactly like @@ -1503,7 +1503,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>load_textfile(Filename)</name> + <name since="">load_textfile(Filename)</name> <fsummary>Loads tables from a text file.</fsummary> <desc> <marker id="load_textfile"></marker> @@ -1516,7 +1516,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name> + <name since="">lock(LockItem, LockKind) -> Nodes | ok | transaction abort</name> <fsummary>Explicit grab lock.</fsummary> <desc> <marker id="lock"></marker> @@ -1605,7 +1605,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>match_object(Pattern) -> transaction abort | RecList</name> + <name since="">match_object(Pattern) -> transaction abort | RecList</name> <fsummary>Matches <c>Pattern</c> for records.</fsummary> <desc> <marker id="match_object_1"></marker> @@ -1614,7 +1614,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name> + <name since="">match_object(Tab, Pattern, LockKind) -> transaction abort | RecList</name> <fsummary>Matches <c>Pattern</c> for records.</fsummary> <desc> <marker id="match_object_3"></marker> @@ -1639,7 +1639,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name> + <name since="">move_table_copy(Tab, From, To) -> {aborted, Reason} | {atomic, ok}</name> <fsummary>Moves the copy of table <c>Tab</c> from node <c>From</c> to node <c>To</c>.</fsummary> <desc> <marker id="move_table_copy"></marker> @@ -1653,7 +1653,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>next(Tab, Key) -> Key | transaction abort</name> + <name since="">next(Tab, Key) -> Key | transaction abort</name> <fsummary>Returns the next key in a table.</fsummary> <desc> <marker id="next"></marker> @@ -1665,7 +1665,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>prev(Tab, Key) -> Key | transaction abort</name> + <name since="">prev(Tab, Key) -> Key | transaction abort</name> <fsummary>Returns the previous key in a table.</fsummary> <desc> <p>Works exactly like @@ -1676,7 +1676,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>read({Tab, Key}) -> transaction abort | RecordList</name> + <name since="">read({Tab, Key}) -> transaction abort | RecordList</name> <fsummary>Reads records(s) with a given key.</fsummary> <desc> <marker id="read_2"></marker> @@ -1684,14 +1684,14 @@ mnesia:create_table(person, </desc> </func> <func> - <name>read(Tab, Key) -> transaction abort | RecordList</name> + <name since="">read(Tab, Key) -> transaction abort | RecordList</name> <fsummary>Reads records(s) with a given key.</fsummary> <desc> <p>Calls function <c>mnesia:read(Tab, Key, read)</c>.</p> </desc> </func> <func> - <name>read(Tab, Key, LockKind) -> transaction abort | RecordList</name> + <name since="">read(Tab, Key, LockKind) -> transaction abort | RecordList</name> <fsummary>Reads records(s) with a given key.</fsummary> <desc> <marker id="read_3"></marker> @@ -1716,7 +1716,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>read_lock_table(Tab) -> ok | transaction abort</name> + <name since="">read_lock_table(Tab) -> ok | transaction abort</name> <fsummary>Sets a read lock on an entire table.</fsummary> <desc> <marker id="read_lock_table"></marker> @@ -1725,7 +1725,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>report_event(Event) -> ok</name> + <name since="">report_event(Event) -> ok</name> <fsummary>Reports a user event to the Mnesia event handler.</fsummary> <desc> <marker id="report_event"></marker> @@ -1743,7 +1743,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name> + <name since="">restore(Opaque, Args) -> {atomic, RestoredTabs} |{aborted, Reason}</name> <fsummary>Online restore of backup.</fsummary> <desc> <marker id="restore"></marker> @@ -1803,7 +1803,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>s_delete({Tab, Key}) -> ok | transaction abort</name> + <name since="">s_delete({Tab, Key}) -> ok | transaction abort</name> <fsummary>Sets sticky lock and delete records.</fsummary> <desc> <marker id="s_delete"></marker> @@ -1812,7 +1812,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>s_delete_object(Record) -> ok | transaction abort</name> + <name since="">s_delete_object(Record) -> ok | transaction abort</name> <fsummary>Sets sticky lock and delete record.</fsummary> <desc> <marker id="s_delete_object"></marker> @@ -1822,7 +1822,7 @@ mnesia:create_table(person, </desc> </func> <func> - <name>s_write(Record) -> ok | transaction abort</name> + <name since="">s_write(Record) -> ok | transaction abort</name> <fsummary>Writes <c>Record</c> and sets sticky lock.</fsummary> <desc> <marker id="s_write"></marker> @@ -1832,21 +1832,21 @@ mnesia:create_table(person, </desc> </func> <func> - <name>schema() -> ok</name> + <name since="">schema() -> ok</name> <fsummary>Prints information about all table definitions on the terminal.</fsummary> <desc> <p>Prints information about all table definitions on the terminal.</p> </desc> </func> <func> - <name>schema(Tab) -> ok</name> + <name since="">schema(Tab) -> ok</name> <fsummary>Prints information about one table definition on the terminal.</fsummary> <desc> <p>Prints information about one table definition on the terminal.</p> </desc> </func> <func> - <name>select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name> + <name since="">select(Tab, MatchSpec [, Lock]) -> transaction abort | [Object]</name> <fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary> <desc> <marker id="select_2_3"></marker> @@ -1884,7 +1884,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name> + <name since="">select(Tab, MatchSpec, NObjects, Lock) -> transaction abort | {[Object],Cont} | '$end_of_table'</name> <fsummary>Matches the objects in <c>Tab</c> against <c>MatchSpec</c>.</fsummary> <desc> <marker id="select_4"></marker> @@ -1907,7 +1907,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name> + <name since="">select(Cont) -> transaction abort | {[Object],Cont} | '$end_of_table'</name> <fsummary>Continues selecting objects.</fsummary> <desc> <p>Selects more objects with the match specification initiated @@ -1919,7 +1919,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>set_debug_level(Level) -> OldLevel</name> + <name since="">set_debug_level(Level) -> OldLevel</name> <fsummary>Changes the internal debug level of Mnesia.</fsummary> <desc> <marker id="set_debug_level"></marker> @@ -1930,7 +1930,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>set_master_nodes(MasterNodes) -> ok | {error, Reason}</name> + <name since="">set_master_nodes(MasterNodes) -> ok | {error, Reason}</name> <fsummary>Sets the master nodes for all tables.</fsummary> <desc> <marker id="set_master_nodes_1"></marker> @@ -1943,7 +1943,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name> + <name since="">set_master_nodes(Tab, MasterNodes) -> ok | {error, Reason}</name> <fsummary>Sets the master nodes for a table.</fsummary> <desc> <marker id="set_master_nodes_2"></marker> @@ -1968,14 +1968,14 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name> + <name since="">snmp_close_table(Tab) -> {aborted, R} | {atomic, ok}</name> <fsummary>Removes the possibility for SNMP to manipulate the table.</fsummary> <desc> <p>Removes the possibility for SNMP to manipulate the table.</p> </desc> </func> <func> - <name>snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name> + <name since="">snmp_get_mnesia_key(Tab, RowIndex) -> {ok, Key} | undefined</name> <fsummary>Gets the corresponding Mnesia key from an SNMP index.</fsummary> <type> <v>Tab ::= atom()</v> @@ -1990,7 +1990,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name> + <name since="">snmp_get_next_index(Tab, RowIndex) -> {ok, NextIndex} | endOfTable</name> <fsummary>Gets the index of the next lexicographical row.</fsummary> <type> <v>Tab ::= atom()</v> @@ -2006,7 +2006,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name> + <name since="">snmp_get_row(Tab, RowIndex) -> {ok, Row} | undefined</name> <fsummary>Retrieves a row indexed by an SNMP index.</fsummary> <type> <v>Tab ::= atom()</v> @@ -2019,7 +2019,7 @@ mnesia:select(Tab,[{MatchHead, [Guard], [Result]}]),</code> </desc> </func> <func> - <name>snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name> + <name since="">snmp_open_table(Tab, SnmpStruct) -> {aborted, R} | {atomic, ok}</name> <fsummary>Organizes a Mnesia table as an SNMP table.</fsummary> <type> <v>Tab ::= atom()</v> @@ -2073,10 +2073,17 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>start() -> ok | {error, Reason}</name> + <name since="">start() -> ok | {error, Reason}</name> <fsummary>Starts a local Mnesia system.</fsummary> <desc> <marker id="start"></marker> + <p>Mnesia startup is asynchronous. The function call + <c>mnesia:start()</c> returns the atom <c>ok</c> and then + starts to initialize the different tables. Depending on the + size of the database, this can take some time, and the + application programmer must wait for the tables that the + application needs before they can be used. This is achieved + by using the function <c>mnesia:wait_for_tables/2</c>.</p> <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 @@ -2108,7 +2115,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>stop() -> stopped</name> + <name since="">stop() -> stopped</name> <fsummary>Stops Mnesia locally.</fsummary> <desc> <marker id="stop"></marker> @@ -2117,7 +2124,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name> + <name since="">subscribe(EventCategory) -> {ok, Node} | {error, Reason}</name> <fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary> <desc> <marker id="subscribe"></marker> @@ -2127,7 +2134,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name> + <name since="">sync_dirty(Fun, [, Args]) -> ResultOfFun | exit(Reason)</name> <fsummary>Calls the <c>Fun</c> in a context that is not protected by a transaction.</fsummary> <desc> <marker id="sync_dirty"></marker> @@ -2143,7 +2150,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>sync_log() -> ok | {error, Reason}</name> + <name since="OTP 17.0">sync_log() -> ok | {error, Reason}</name> <fsummary>Performs a file sync of the local log file.</fsummary> <desc> <p>Ensures that the local transaction log file is synced to disk. @@ -2153,7 +2160,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name> + <name since="">sync_transaction(Fun, [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name> <fsummary>Synchronously executes a transaction.</fsummary> <desc> <marker id="sync_transaction"></marker> @@ -2166,7 +2173,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>system_info(InfoKey) -> Info | exit({aborted, Reason})</name> + <name since="">system_info(InfoKey) -> Info | exit({aborted, Reason})</name> <fsummary>Returns information about the Mnesia system.</fsummary> <desc> <marker id="system_info"></marker> @@ -2353,7 +2360,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>table(Tab [,[Option]]) -> QueryHandle</name> + <name since="">table(Tab [,[Option]]) -> QueryHandle</name> <fsummary>Return a QLC query handle.</fsummary> <desc> <marker id="table"></marker> @@ -2408,7 +2415,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name> + <name since="">table_info(Tab, InfoKey) -> Info | exit({aborted, Reason})</name> <fsummary>Returns local information about table.</fsummary> <desc> <marker id="table_info"></marker> @@ -2560,7 +2567,7 @@ mnesia:create_table(employee, </desc> </func> <func> - <name>transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name> + <name since="">transaction(Fun [[, Args], Retries]) -> {aborted, Reason} | {atomic, ResultOfFun}</name> <fsummary>Executes a transaction.</fsummary> <desc> <marker id="transaction"></marker> @@ -2628,7 +2635,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name> + <name since="">transform_table(Tab, Fun, NewAttributeList, NewRecordName) -> {aborted, R} | {atomic, ok}</name> <fsummary>Changes format on all records in table <c>Tab</c>.</fsummary> <desc> <marker id="transform_table_4"></marker> @@ -2649,7 +2656,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name> + <name since="">transform_table(Tab, Fun, NewAttributeList) -> {aborted, R} | {atomic, ok}</name> <fsummary>Changes format on all records in table <c>Tab</c>.</fsummary> <desc> <p>Calls <c>mnesia:transform_table(Tab, Fun, @@ -2658,7 +2665,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name> + <name since="">traverse_backup(Source, [SourceMod,] Target, [TargetMod,] Fun, Acc) -> {ok, LastAcc} | {error, Reason}</name> <fsummary>Traversal of a backup.</fsummary> <desc> <marker id="traverse_backup"></marker> @@ -2689,7 +2696,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>uninstall_fallback() -> ok | {error,Reason}</name> + <name since="">uninstall_fallback() -> ok | {error,Reason}</name> <fsummary>Uninstalls a fallback.</fsummary> <desc> <marker id="uninstall_fallback_0"></marker> @@ -2698,7 +2705,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>uninstall_fallback(Args) -> ok | {error,Reason}</name> + <name since="">uninstall_fallback(Args) -> ok | {error,Reason}</name> <fsummary>Uninstalls a fallback.</fsummary> <desc> <p>Deinstalls a fallback before it @@ -2725,7 +2732,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name> + <name since="">unsubscribe(EventCategory) -> {ok, Node} | {error, Reason}</name> <fsummary>Subscribes to events of type <c>EventCategory</c>.</fsummary> <desc> <marker id="unsubscribe"></marker> @@ -2735,7 +2742,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name> + <name since="">wait_for_tables(TabList, Timeout) -> ok | {timeout, BadTabList} | {error, Reason}</name> <fsummary>Waits for tables to be accessible.</fsummary> <desc> <marker id="wait_for_tables"></marker> @@ -2746,7 +2753,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>wread({Tab, Key}) -> transaction abort | RecordList</name> + <name since="">wread({Tab, Key}) -> transaction abort | RecordList</name> <fsummary>Reads records with given key.</fsummary> <desc> <marker id="wread"></marker> @@ -2754,7 +2761,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>write(Record) -> transaction abort | ok</name> + <name since="">write(Record) -> transaction abort | ok</name> <fsummary>Writes a record into the database.</fsummary> <desc> <marker id="write_1"></marker> @@ -2763,7 +2770,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>write(Tab, Record, LockKind) -> transaction abort | ok</name> + <name since="">write(Tab, Record, LockKind) -> transaction abort | ok</name> <fsummary>Writes a record into the database.</fsummary> <desc> <marker id="write_3"></marker> @@ -2779,7 +2786,7 @@ raise(Name, Amount) -> </desc> </func> <func> - <name>write_lock_table(Tab) -> ok | transaction abort</name> + <name since="">write_lock_table(Tab) -> ok | transaction abort</name> <fsummary>Sets write lock on an entire table.</fsummary> <desc> <marker id="write_lock_table"></marker> diff --git a/lib/mnesia/doc/src/mnesia_frag_hash.xml b/lib/mnesia/doc/src/mnesia_frag_hash.xml index c233acdb05..ccaba412d0 100644 --- a/lib/mnesia/doc/src/mnesia_frag_hash.xml +++ b/lib/mnesia/doc/src/mnesia_frag_hash.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>mnesia_frag_hash.sgml</file> </header> - <module>mnesia_frag_hash</module> + <module since="">mnesia_frag_hash</module> <modulesummary>Defines mnesia_frag_hash callback behavior</modulesummary> <description> <p>This module defines a callback behavior for user-defined hash @@ -50,7 +50,7 @@ <funcs> <func> - <name>init_state(Tab, State) -> NewState | abort(Reason)</name> + <name since="">init_state(Tab, State) -> NewState | abort(Reason)</name> <fsummary>Initiates the hash state for a new table.</fsummary> <type> <v>Tab = atom()</v> @@ -72,7 +72,7 @@ </desc> </func> <func> - <name>add_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name> + <name since="">add_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name> <fsummary>Starts when a new fragment is added to a fragmented table.</fsummary> <type> <v>State = term()</v> @@ -100,7 +100,7 @@ </desc> </func> <func> - <name>del_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name> + <name since="">del_frag(State) -> {NewState, IterFrags, AdditionalLockFrags} | abort(Reason)</name> <fsummary>Starts when a fragment is deleted from a fragmented table.</fsummary> <type> <v>State = term()</v> @@ -127,7 +127,7 @@ </desc> </func> <func> - <name>key_to_frag_number(State, Key) -> FragNum | abort(Reason)</name> + <name since="">key_to_frag_number(State, Key) -> FragNum | abort(Reason)</name> <fsummary>Resolves the key of a record into a fragment number.</fsummary> <type> <v>FragNum = integer()</v> @@ -140,7 +140,7 @@ </desc> </func> <func> - <name>match_spec_to_frag_numbers(State, MatchSpec) -> FragNums | abort(Reason)</name> + <name since="">match_spec_to_frag_numbers(State, MatchSpec) -> FragNums | abort(Reason)</name> <fsummary>Resolves a <c>MatchSpec</c> into a list of fragment numbers.</fsummary> <type> <v>MatcSpec = ets_select_match_spec()</v> diff --git a/lib/mnesia/doc/src/mnesia_registry.xml b/lib/mnesia/doc/src/mnesia_registry.xml index a76f716981..18ddc4ab9e 100644 --- a/lib/mnesia/doc/src/mnesia_registry.xml +++ b/lib/mnesia/doc/src/mnesia_registry.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>mnesia_registry.sgml</file> </header> - <module>mnesia_registry</module> + <module since="">mnesia_registry</module> <modulesummary>Dump support for registries in erl_interface.</modulesummary> <description> <p>This module is usually part of the <c>erl_interface</c> @@ -57,7 +57,7 @@ <funcs> <func> - <name>create_table(Tab) -> ok | exit(Reason)</name> + <name since="">create_table(Tab) -> ok | exit(Reason)</name> <fsummary>Creates a registry table in Mnesia.</fsummary> <desc> <p>A wrapper function for <c>mnesia:create_table/2</c>, @@ -73,7 +73,7 @@ </desc> </func> <func> - <name>create_table(Tab, TabDef) -> ok | exit(Reason)</name> + <name since="">create_table(Tab, TabDef) -> ok | exit(Reason)</name> <fsummary>Creates a customized registry table in Mnesia.</fsummary> <desc> <p>A wrapper function for <c>mnesia:create_table/2</c>, diff --git a/lib/observer/doc/src/crashdump.xml b/lib/observer/doc/src/crashdump.xml index 48f944cbce..62c6ff1f25 100644 --- a/lib/observer/doc/src/crashdump.xml +++ b/lib/observer/doc/src/crashdump.xml @@ -34,7 +34,7 @@ <rev>PA1</rev> <file>crashdump.xml</file> </header> - <module>crashdump_viewer</module> + <module since="">crashdump_viewer</module> <modulesummary>A WxWidgets based tool for browsing Erlang crashdumps.</modulesummary> <description> @@ -46,8 +46,8 @@ </description> <funcs> <func> - <name>start() -> ok</name> - <name>start(File) -> ok</name> + <name since="">start() -> ok</name> + <name since="OTP 17.0">start(File) -> ok</name> <fsummary>Start the Crashdump Viewer.</fsummary> <type> <v>File = string()</v> @@ -62,7 +62,7 @@ </desc> </func> <func> - <name>stop() -> ok</name> + <name since="">stop() -> ok</name> <fsummary>Terminate the Crashdump Viewer.</fsummary> <desc> <p>Terminates the Crashdump Viewer and closes diff --git a/lib/observer/doc/src/etop.xml b/lib/observer/doc/src/etop.xml index e7a83d0514..f0acc7b5d8 100644 --- a/lib/observer/doc/src/etop.xml +++ b/lib/observer/doc/src/etop.xml @@ -34,7 +34,7 @@ <rev></rev> <file></file> </header> - <module>etop</module> + <module since="">etop</module> <modulesummary>Erlang Top is a tool for presenting information about Erlang processes similar to the information presented by "top" in UNIX.</modulesummary> <description> @@ -98,7 +98,7 @@ </description> <funcs> <func> - <name>start() -> ok</name> + <name since="OTP R15B01">start() -> ok</name> <fsummary>Start etop.</fsummary> <desc> <p>Starts <c>etop</c>. @@ -106,7 +106,7 @@ </desc> </func> <func> - <name>start(Options) -> ok</name> + <name since="OTP R15B01">start(Options) -> ok</name> <fsummary>Start etop.</fsummary> <type> <v>Options = [Option]</v> @@ -120,7 +120,7 @@ </desc> </func> <func> - <name>help() -> ok</name> + <name since="OTP R15B01">help() -> ok</name> <fsummary>Display the etop help.</fsummary> <desc> <p>Displays the help of <c>etop</c> and @@ -128,7 +128,7 @@ </desc> </func> <func> - <name>config(Key,Value) -> Result</name> + <name since="">config(Key,Value) -> Result</name> <fsummary>Change the configuration of the tool.</fsummary> <type> <v>Result = ok | {error,Reason}</v> @@ -142,7 +142,7 @@ </desc> </func> <func> - <name>dump(File) -> Result</name> + <name since="">dump(File) -> Result</name> <fsummary>Dump the current display to a file.</fsummary> <type> <v>Result = ok | {error,Reason}</v> @@ -153,7 +153,7 @@ </desc> </func> <func> - <name>stop() -> stop</name> + <name since="">stop() -> stop</name> <fsummary>Terminate etop.</fsummary> <desc> <p>Terminates <c>etop</c>.</p> diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index 4d1a9a4f55..22035af982 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -32,6 +32,43 @@ <p>This document describes the changes made to the Observer application.</p> +<section><title>Observer 2.8.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Literals such as <c>#{"one"=>1}</c> dumped to a crash + dump would cause <c>crashdump_viewer</c> to crash.</p> + <p> + Own Id: OTP-15365 Aux Id: ERL-722 </p> + </item> + <item> + <p> + <c>crashdump_viewer</c> would sometimes crash when + processing a dump which was truncated in the + <c>literals</c> area. This is now corrected.</p> + <p> + Own Id: OTP-15377</p> + </item> + <item> + <p> + Since OTP-20.2, <c>crashdump_viewer</c> was very slow + when opening a crash dump with many processes. An + ets:select per process could be removed, which improved + the performance a lot.</p> + <p> + A bug when parsing heap data in a crashdump caused + <c>crashdump_viewer</c> to crash when multiple <c>Yc</c> + lines referenced the same reference counted binary. This + is now corrected.</p> + <p> + Own Id: OTP-15391</p> + </item> + </list> + </section> + +</section> + <section><title>Observer 2.8.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/observer/doc/src/observer.xml b/lib/observer/doc/src/observer.xml index 843be26ee1..7fb1dd044e 100644 --- a/lib/observer/doc/src/observer.xml +++ b/lib/observer/doc/src/observer.xml @@ -33,7 +33,7 @@ <rev>PA1</rev> <file>observer.xml</file> </header> - <module>observer</module> + <module since="OTP R15B">observer</module> <modulesummary>A GUI tool for observing an Erlang system.</modulesummary> <description> <p>Observer is a graphical tool for observing the characteristics of @@ -48,7 +48,7 @@ </description> <funcs> <func> - <name>start() -> ok</name> + <name since="OTP R15B">start() -> ok</name> <fsummary>Start the Observer GUI.</fsummary> <desc> <p>Starts the Observer GUI. diff --git a/lib/observer/doc/src/ttb.xml b/lib/observer/doc/src/ttb.xml index 7cd15e15d3..fee95e0b21 100644 --- a/lib/observer/doc/src/ttb.xml +++ b/lib/observer/doc/src/ttb.xml @@ -33,7 +33,7 @@ <rev>PA1</rev> <file>ttb.xml</file> </header> - <module>ttb</module> + <module since="">ttb</module> <modulesummary>A base for building trace tools for distributed systems.</modulesummary> <description> <p>The Trace Tool Builder, <c>ttb</c>, is a base for building trace @@ -44,7 +44,7 @@ </description> <funcs> <func> - <name>start_trace(Nodes, Patterns, FlagSpec, Opts) -> Result</name> + <name since="OTP R15B">start_trace(Nodes, Patterns, FlagSpec, Opts) -> Result</name> <fsummary>Start a trace port on each specified node.</fsummary> <type> <v>Result = see p/2</v> @@ -76,7 +76,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>tracer() -> Result</name> + <name since="">tracer() -> Result</name> <fsummary>Equivalent to tracer(node()).</fsummary> <desc> <p>Equivalent to <c>tracer(node())</c>.</p> @@ -84,7 +84,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>tracer(Shortcut) -> Result</name> + <name since="">tracer(Shortcut) -> Result</name> <fsummary>Handy shortcuts for common tracing settings.</fsummary> <type> <v>Shortcut = shell | dbg</v> @@ -97,7 +97,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>tracer(Nodes) -> Result</name> + <name since="">tracer(Nodes) -> Result</name> <fsummary>Equivalent to tracer(Nodes,[]).</fsummary> <desc> <p>Equivalent to <c>tracer(Nodes,[])</c>.</p> @@ -105,7 +105,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>tracer(Nodes,Opts) -> Result</name> + <name since="">tracer(Nodes,Opts) -> Result</name> <fsummary>Start a trace port on each specified node.</fsummary> <type> <v>Result = {ok, ActivatedNodes} | {error,Reason}</v> @@ -243,7 +243,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>p(Item,Flags) -> Return</name> + <name since="">p(Item,Flags) -> Return</name> <fsummary>Set the specified trace flags on the specified processes or ports.</fsummary> <type> <v>Return = {ok,[{Item,MatchDesc}]}</v> @@ -277,7 +277,8 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>tp, tpl, tpe, ctp, ctpl, ctpg, ctpe</name> + <name since="">tp, tpl, ctp, ctpl, ctpg</name> + <name since="OTP 19.0">tpe, ctpe</name> <fsummary>Set and clear trace patterns.</fsummary> <desc> <p>These functions are to be used with trace @@ -338,7 +339,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>list_history() -> History</name> + <name since="">list_history() -> History</name> <fsummary>Return all calls stored in history.</fsummary> <type> <v>History = [{N,Func,Args}]</v> @@ -352,7 +353,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>run_history(N) -> ok | {error, Reason}</name> + <name since="">run_history(N) -> ok | {error, Reason}</name> <fsummary>Execute one entry of the history.</fsummary> <type> <v>N = integer() | [integer()]</v> @@ -364,7 +365,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>write_config(ConfigFile,Config)</name> + <name since="">write_config(ConfigFile,Config)</name> <fsummary>Equivalent to write_config(ConfigFile,Config,[]).</fsummary> <desc> <p>Equivalent to <c>write_config(ConfigFile,Config,[])</c>.</p> @@ -372,7 +373,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>write_config(ConfigFile,Config,Opts) -> ok | {error,Reason}</name> + <name since="">write_config(ConfigFile,Config,Opts) -> ok | {error,Reason}</name> <fsummary>Create a configuration file.</fsummary> <type> <v>ConfigFile = string()</v> @@ -405,7 +406,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>run_config(ConfigFile) -> ok | {error,Reason}</name> + <name since="">run_config(ConfigFile) -> ok | {error,Reason}</name> <fsummary>Execute all entries in a configuration file.</fsummary> <type> <v>ConfigFile = string()</v> @@ -418,7 +419,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>run_config(ConfigFile,NumList) -> ok | {error,Reason}</name> + <name since="">run_config(ConfigFile,NumList) -> ok | {error,Reason}</name> <fsummary>Execute selected entries from a configuration file.</fsummary> <type> <v>ConfigFile = string()</v> @@ -437,7 +438,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>list_config(ConfigFile) -> Config | {error,Reason}</name> + <name since="">list_config(ConfigFile) -> Config | {error,Reason}</name> <fsummary>List all entries in a configuration file.</fsummary> <type> <v>ConfigFile = string()</v> @@ -449,7 +450,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>write_trace_info(Key,Info) -> ok</name> + <name since="">write_trace_info(Key,Info) -> ok</name> <fsummary>Write any information to file <c>.ti</c>.</fsummary> <type> <v>Key = term()</v> @@ -465,7 +466,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>seq_trigger_ms() -> MatchSpec</name> + <name since="">seq_trigger_ms() -> MatchSpec</name> <fsummary>Equivalent to seq_trigger_ms(all).</fsummary> <desc> <p>Equivalent to <c>seq_trigger_ms(all)</c>.</p> @@ -473,7 +474,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>seq_trigger_ms(Flags) -> MatchSpec</name> + <name since="">seq_trigger_ms(Flags) -> MatchSpec</name> <fsummary>Return a match_spec() which starts sequential tracing.</fsummary> <type> <v>MatchSpec = match_spec()</v> @@ -521,7 +522,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>stop()</name> + <name since="">stop()</name> <fsummary>Equivalent to stop([]).</fsummary> <desc> <p>Equivalent to <c>stop([])</c>.</p> @@ -529,7 +530,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>stop(Opts) -> stopped | {stopped, Dir}</name> + <name since="">stop(Opts) -> stopped | {stopped, Dir}</name> <fsummary>Stop tracing and fetch/format logs from all nodes.</fsummary> <type> <v>Opts = Opt | [Opt]</v> @@ -573,7 +574,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>get_et_handler()</name> + <name since="OTP R15B">get_et_handler()</name> <fsummary>Return the <c>et</c> handler.</fsummary> <desc> <p>Returns the <c>et</c> handler, which can be used with <c>format/2</c> @@ -583,7 +584,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>format(File)</name> + <name since="">format(File)</name> <fsummary>Equivalent to <c>format(File,[])</c>.</fsummary> <desc> <p>Equivalent to <c>format(File,[])</c>.</p> @@ -591,7 +592,7 @@ ttb:p(all, call).</input></pre> </func> <func> - <name>format(File,Options) -> ok | {error, Reason}</name> + <name since="">format(File,Options) -> ok | {error, Reason}</name> <fsummary>Format a binary trace log.</fsummary> <type> <v>File = string() | [string()]</v> diff --git a/lib/observer/src/cdv_html_wx.erl b/lib/observer/src/cdv_html_wx.erl index ffef83227c..8956173c93 100644 --- a/lib/observer/src/cdv_html_wx.erl +++ b/lib/observer/src/cdv_html_wx.erl @@ -79,14 +79,14 @@ handle_info(active, #state{panel=HtmlWin,delayed_fetch=Callback}=State) observer_lib:sync_destroy_progress_dialog(), wx_misc:beginBusyCursor(), wxHtmlWindow:setPage(HtmlWin,HtmlText), - cdv_wx:set_status(TW), + cdv_wx_set_status(State, TW), wx_misc:endBusyCursor(), {noreply, State#state{expand_table=Tab, delayed_fetch=undefined, trunc_warn=TW}}; handle_info(active, State) -> - cdv_wx:set_status(State#state.trunc_warn), + cdv_wx_set_status(State, State#state.trunc_warn), {noreply, State}; handle_info(Info, State) -> @@ -164,3 +164,10 @@ expand(Id,Callback,#state{expand_wins=Opened0, app=App}=State) -> Opened0 end, State#state{expand_wins=Opened}. + +cdv_wx_set_status(#state{app = cdv}, Status) -> + %% this module is used by the observer when cdw_wx isn't started + %% only try to set status when used by cdv + cdv_wx:set_status(Status); +cdv_wx_set_status(_, _) -> + ok. diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl index 0627c15b1c..97bb344cbf 100644 --- a/lib/observer/src/crashdump_viewer.erl +++ b/lib/observer/src/crashdump_viewer.erl @@ -589,9 +589,9 @@ truncated_here(Tag) -> case get(truncated) of true -> case get(last_tag) of - Tag -> % Tag == {TagType,Id} + {Tag,_Pos} -> % Tag == {TagType,Id} true; - {Tag,_Id} -> + {{Tag,_Id},_Pos} -> true; _LastTag -> truncated_earlier(Tag) @@ -846,8 +846,8 @@ do_read_file(File) -> case check_dump_version(Id) of {ok,DumpVsn} -> reset_tables(), - insert_index(Tag,Id,N1+1), - put_last_tag(Tag,""), + insert_index(Tag,Id,Pos=N1+1), + put_last_tag(Tag,"",Pos), DecodeOpts = get_decode_opts(DumpVsn), indexify(Fd,DecodeOpts,Rest,N1), end_progress(), @@ -915,23 +915,11 @@ indexify(Fd,DecodeOpts,Bin,N) -> _ -> insert_index(Tag,Id,NewPos) end, - case put_last_tag(Tag,Id) of - {?proc_heap,LastId} -> - [{_,LastPos}] = lookup_index(?proc_heap,LastId), + case put_last_tag(Tag,Id,NewPos) of + {{?proc_heap,LastId},LastPos} -> ets:insert(cdv_heap_file_chars,{LastId,N+Start+1-LastPos}); - {?literals,[]} -> - case get(truncated_reason) of - undefined -> - [{_,LastPos}] = lookup_index(?literals,[]), - ets:insert(cdv_heap_file_chars, - {literals,N+Start+1-LastPos}); - _ -> - %% Literals are truncated. Make sure we never - %% attempt to read in the literals. (Heaps that - %% references literals will show markers for - %% incomplete heaps, but will otherwise work.) - delete_index(?literals, []) - end; + {{?literals,[]},LastPos} -> + ets:insert(cdv_heap_file_chars,{literals,N+Start+1-LastPos}); _ -> ok end, indexify(Fd,DecodeOpts,Rest,N1); @@ -973,10 +961,18 @@ tag(Fd,<<>>,N,Gat,Di,Now) -> check_if_truncated() -> case get(last_tag) of - {?ende,_} -> + {{?ende,_},_} -> put(truncated,false), put(truncated_proc,false); - TruncatedTag -> + {{?literals,[]},_} -> + put(truncated,true), + put(truncated_proc,false), + %% Literals are truncated. Make sure we never + %% attempt to read in the literals. (Heaps that + %% references literals will show markers for + %% incomplete heaps, but will otherwise work.) + delete_index(?literals, []); + {TruncatedTag,_} -> put(truncated,true), find_truncated_proc(TruncatedTag) end. @@ -984,7 +980,6 @@ check_if_truncated() -> find_truncated_proc({Tag,_Id}) when Tag==?atoms; Tag==?binary; Tag==?instr_data; - Tag==?literals; Tag==?memory_status; Tag==?memory_map -> put(truncated_proc,false); @@ -2870,12 +2865,12 @@ parse_heap_term("Yc"++Line0, Addr, DecodeOpts, D0) -> %Reference-counted binary. SymbolicBin = {'#CDVBin',Start}, Term = cdvbin(Offset, Sz, SymbolicBin), D1 = gb_trees:insert(Addr, Term, D0), - D = gb_trees:insert(Binp, SymbolicBin, D1), + D = gb_trees:enter(Binp, SymbolicBin, D1), {Term,Line,D}; [] -> Term = '#CDVNonexistingBinary', D1 = gb_trees:insert(Addr, Term, D0), - D = gb_trees:insert(Binp, Term, D1), + D = gb_trees:enter(Binp, Term, D1), {Term,Line,D} end; parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) -> %Sub binary. @@ -2887,12 +2882,17 @@ parse_heap_term("Ys"++Line0, Addr, DecodeOpts, D0) -> %Sub binary. {Term,Line,D}; parse_heap_term("Mf"++Line0, Addr, DecodeOpts, D0) -> %Flatmap. {Size,":"++Line1} = get_hex(Line0), - {Keys,":"++Line2,D1} = parse_term(Line1, DecodeOpts, D0), - {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []), - Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []), - Map = maps:from_list(Pairs), - D = gb_trees:update(Addr, Map, D2), - {Map,Line,D}; + case parse_term(Line1, DecodeOpts, D0) of + {Keys,":"++Line2,D1} when is_tuple(Keys) -> + {Values,Line,D2} = parse_tuple(Size, Line2, Addr,DecodeOpts, D1, []), + Pairs = zip_tuples(tuple_size(Keys), Keys, Values, []), + Map = maps:from_list(Pairs), + D = gb_trees:update(Addr, Map, D2), + {Map,Line,D}; + {Incomplete,_Line,D1} -> + D = gb_trees:insert(Addr, Incomplete, D1), + {Incomplete,"",D} + end; parse_heap_term("Mh"++Line0, Addr, DecodeOpts, D0) -> %Head node in a hashmap. {MapSize,":"++Line1} = get_hex(Line0), {N,":"++Line2} = get_hex(Line1), @@ -3051,7 +3051,7 @@ do_deref_ptr(Lookup, Line, DecodeOpts, D0) -> {Term,Line,D0}; none -> put(incomplete_heap, true), - {['#CDVIncompleteHeap'],Line,D0}; + {'#CDVIncompleteHeap',Line,D0}; {line,Addr,NewLine} -> D = parse_line(Addr, NewLine, DecodeOpts, D0), do_deref_ptr(Lookup, Line, DecodeOpts, D) @@ -3275,13 +3275,13 @@ tag_to_atom(UnknownTag) -> %%%----------------------------------------------------------------- %%% Store last tag for use when truncated, and reason if aborted -put_last_tag(?abort,Reason) -> - %% Don't overwrite the real last tag, and make sure to return - %% the previous last tag. - put(truncated_reason,Reason), - get(last_tag); -put_last_tag(Tag,Id) -> - put(last_tag,{Tag,Id}). +put_last_tag(?abort,Reason,_Pos) -> + %% Don't overwrite the real last tag, and don't return it either, + %% since that would make the caller of this function believe that + %% the tag was complete. + put(truncated_reason,Reason); +put_last_tag(Tag,Id,Pos) -> + put(last_tag,{{Tag,Id},Pos}). %%%----------------------------------------------------------------- %%% Fetch next chunk from crashdump file diff --git a/lib/observer/test/crashdump_helper.erl b/lib/observer/test/crashdump_helper.erl index dd50bddf38..d6b5eff9b5 100644 --- a/lib/observer/test/crashdump_helper.erl +++ b/lib/observer/test/crashdump_helper.erl @@ -64,6 +64,7 @@ n1_proc(Creator,_N2,Pid2,Port2,_L) -> put(ref,Ref), put(pid,Pid), put(bin,Bin), + put(proc_bins,create_proc_bins()), put(bins,create_binaries()), put(sub_bin,SubBin), put(sub_bins,create_sub_binaries(get(bins))), @@ -119,6 +120,23 @@ create_sub_binary(Bin, Start, LenSub) -> <<_:Start/bytes,Sub:Len/bytes,_/bytes>> = Bin, Sub. +create_proc_bins() -> + Parent = self(), + Pid = + spawn( + fun() -> + %% Just reverse the list here, so this binary is not + %% confused with the one created in n1_proc/5 above, + %% which is used for testing truncation (see + %% crashdump_viewer_SUITE:truncate_dump_binary/1) + Bin = list_to_binary(lists:reverse(lists:seq(1, 255))), + <<A:65/bytes,B:65/bytes,C/bytes>> = Bin, + Parent ! {self(),{A,B,C}} + end), + receive + {Pid,ProcBins} -> ProcBins + end. + %%% %%% Test dumping of maps. Dumping of maps only from OTP 20.2. %%% @@ -186,4 +204,4 @@ dump_persistent_terms() -> create_persistent_terms() -> persistent_term:put({?MODULE,first}, {pid,42.0}), persistent_term:put({?MODULE,second}, [1,2,3]), - persistent_term:get(). + {persistent_term:get({?MODULE,first}),persistent_term:get({?MODULE,second})}. diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl index ed53c46a0d..31cf7011d4 100644 --- a/lib/observer/test/crashdump_viewer_SUITE.erl +++ b/lib/observer/test/crashdump_viewer_SUITE.erl @@ -400,6 +400,13 @@ special(File,Procs) -> crashdump_viewer:expand_binary({SOffset,SSize,SPos}), io:format(" expand binary ok",[]), + ProcBins = proplists:get_value(proc_bins,Dict), + {['#CDVBin',0,65,ProcBin], + ['#CDVBin',65,65,ProcBin], + ['#CDVBin',130,125,ProcBin]} = ProcBins, + io:format(" ProcBins ok",[]), + + Binaries = crashdump_helper:create_binaries(), verify_binaries(Binaries, proplists:get_value(bins,Dict)), io:format(" binaries ok",[]), @@ -608,9 +615,8 @@ special(File,Procs) -> #proc{dict=Dict} = ProcDetails, %% io:format("~p\n", [Dict]), - Pts1 = crashdump_helper:create_persistent_terms(), - Pts2 = proplists:get_value(pts,Dict), - true = lists:sort(Pts1) =:= lists:sort(Pts2), + Pts = crashdump_helper:create_persistent_terms(), + Pts = proplists:get_value(pts,Dict), io:format(" persistent terms ok",[]), ok; _ -> diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index 21c8f4e6c9..5ce0aca589 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 2.8.1 +OBSERVER_VSN = 2.8.2 diff --git a/lib/odbc/c_src/odbcserver.c b/lib/odbc/c_src/odbcserver.c index 8c799f6ff1..fb4f61417e 100644 --- a/lib/odbc/c_src/odbcserver.c +++ b/lib/odbc/c_src/odbcserver.c @@ -2749,6 +2749,11 @@ static diagnos get_diagnos(SQLSMALLINT handleType, SQLHANDLE handle, Boolean ext errmsg_buffer_size = errmsg_buffer_size - errmsg_size; acc_errmsg_size = acc_errmsg_size + errmsg_size; current_errmsg_pos = current_errmsg_pos + errmsg_size; + } else if(result == SQL_SUCCESS_WITH_INFO && errmsg_size >= errmsg_buffer_size) { + memcpy(diagnos.sqlState, current_sql_state, SQL_STATE_SIZE); + diagnos.nativeError = nativeError; + acc_errmsg_size = errmsg_buffer_size; + break; } else { break; } diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml index 9760908855..1a3063e6ce 100644 --- a/lib/odbc/doc/src/odbc.xml +++ b/lib/odbc/doc/src/odbc.xml @@ -29,7 +29,7 @@ <date></date> <rev></rev> </header> - <module>odbc</module> + <module since="">odbc</module> <modulesummary>Erlang ODBC application</modulesummary> <description> <p>This application provides an Erlang interface to communicate @@ -130,8 +130,8 @@ </section> <funcs> <func> - <name>commit(Ref, CommitMode) -></name> - <name>commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name> + <name since="">commit(Ref, CommitMode) -></name> + <name since="">commit(Ref, CommitMode, TimeOut) -> ok | {error, Reason} </name> <fsummary>Commits or rollbacks a transaction. </fsummary> <type> <v>Ref = connection_reference() </v> @@ -145,7 +145,7 @@ </desc> </func> <func> - <name>connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name> + <name since="">connect(ConnectStr, Options) -> {ok, Ref} | {error, Reason} </name> <fsummary>Opens a connection to the database. </fsummary> <type> <v>ConnectStr = string()</v> @@ -236,7 +236,7 @@ </desc> </func> <func> - <name>disconnect(Ref) -> ok | {error, Reason} </name> + <name since="">disconnect(Ref) -> ok | {error, Reason} </name> <fsummary>Closes a connection to a database. </fsummary> <type> <v>Ref = connection_reference()</v> @@ -255,8 +255,8 @@ </desc> </func> <func> - <name>describe_table(Ref, Table) -> </name> - <name>describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name> + <name since="">describe_table(Ref, Table) -> </name> + <name since="">describe_table(Ref, Table, Timeout) -> {ok, Description} | {error, Reason} </name> <fsummary>Queries the database to find out the data types of the columns of the table <c>Table</c>. </fsummary> <type> <v>Ref = connection_reference()</v> @@ -271,8 +271,8 @@ </desc> </func> <func> - <name>first(Ref) -></name> - <name>first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name> + <name since="">first(Ref) -></name> + <name since="">first(Ref, Timeout) -> {selected, ColNames, Rows} | {error, Reason} </name> <fsummary>Returns the first row of the result set and positions a cursor at this row.</fsummary> <type> <v>Ref = connection_reference()</v> @@ -287,8 +287,8 @@ </desc> </func> <func> - <name>last(Ref) -></name> - <name>last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <name since="">last(Ref) -></name> + <name since="">last(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> <fsummary>Returns the last row of the result set and positions a cursor at this row. </fsummary> <type> <v>Ref = connection_reference()</v> @@ -303,8 +303,8 @@ </desc> </func> <func> - <name>next(Ref) -> </name> - <name>next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <name since="">next(Ref) -> </name> + <name since="">next(Ref, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> <fsummary>Returns the next row of the result set relative the current cursor position and positions the cursor at this row. </fsummary> <type> <v>Ref = connection_reference()</v> @@ -325,8 +325,8 @@ </desc> </func> <func> - <name>param_query(Ref, SQLQuery, Params) -> </name> - <name>param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name> + <name since="">param_query(Ref, SQLQuery, Params) -> </name> + <name since="">param_query(Ref, SQLQuery, Params, TimeOut) -> ResultTuple | {error, Reason} </name> <fsummary>Executes a parameterized SQL query.</fsummary> <type> <v>Ref = connection_reference()</v> @@ -353,8 +353,8 @@ </desc> </func> <func> - <name>prev(Ref) -> </name> - <name>prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <name since="">prev(Ref) -> </name> + <name since="">prev(ConnectionReference, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> <fsummary>Returns the previous row of the result set relative the current cursor position and positions the cursor at this row. </fsummary> <type> <v>Ref = connection_reference()</v> @@ -371,8 +371,8 @@ </func> <func> - <name>start() -> </name> - <name>start(Type) -> ok | {error, Reason}</name> + <name since="">start() -> </name> + <name since="">start(Type) -> ok | {error, Reason}</name> <fsummary>Starts the odb application. </fsummary> <type> @@ -389,7 +389,7 @@ </func> <func> - <name>stop() -> ok </name> + <name since="">stop() -> ok </name> <fsummary> Stops the odbc application.</fsummary> <desc> @@ -400,8 +400,8 @@ </func> <func> - <name>sql_query(Ref, SQLQuery) -> </name> - <name>sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name> + <name since="">sql_query(Ref, SQLQuery) -> </name> + <name since="">sql_query(Ref, SQLQuery, TimeOut) -> ResultTuple | [ResultTuple] |{error, Reason}</name> <fsummary>Executes a SQL query or a batch of SQL queries. If it is a SELECT query the result set is returned, on the format<c>{selected, ColNames, Rows}</c>. For other query types the tuple <c>{updated, NRows}</c>is returned, and for batched queries, if the driver supports them, this function can also return a list of result tuples.</fsummary> <type> <v>Ref = connection_reference()</v> @@ -429,8 +429,8 @@ </desc> </func> <func> - <name>select_count(Ref, SelectQuery) -> </name> - <name>select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name> + <name since="">select_count(Ref, SelectQuery) -> </name> + <name since="">select_count(Ref, SelectQuery, TimeOut) -> {ok, NrRows} | {error, Reason} </name> <fsummary>Executes a SQL SELECT query and associates the result set with the connection. A cursor is positioned before the first row in the result set and the tuple <c>{ok, NrRows}</c>is returned. </fsummary> <type> <v>Ref = connection_reference()</v> @@ -453,8 +453,8 @@ </desc> </func> <func> - <name>select(Ref, Position, N) -></name> - <name>select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> + <name since="">select(Ref, Position, N) -></name> + <name since="">select(Ref, Position, N, TimeOut) -> {selected, ColNames, Rows} | {error, Reason} </name> <fsummary>Selects <c>N</c>consecutive rows of the result set.</fsummary> <type> <v>Ref = connection_reference()</v> diff --git a/lib/os_mon/doc/src/cpu_sup.xml b/lib/os_mon/doc/src/cpu_sup.xml index bada165a06..b7adb2bcd2 100644 --- a/lib/os_mon/doc/src/cpu_sup.xml +++ b/lib/os_mon/doc/src/cpu_sup.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>cpu_sup</module> + <module since="">cpu_sup</module> <modulesummary>A CPU Load and CPU Utilization Supervisor Process</modulesummary> <description> <p><c>cpu_sup</c> is a process which supervises the CPU load @@ -76,7 +76,7 @@ </description> <funcs> <func> - <name>nprocs() -> UnixProcesses | {error, Reason}</name> + <name since="">nprocs() -> UnixProcesses | {error, Reason}</name> <fsummary>Get the number of UNIX processes running on this host</fsummary> <type> <v>UnixProcesses = int()</v> @@ -90,7 +90,7 @@ </desc> </func> <func> - <name>avg1() -> SystemLoad | {error, Reason}</name> + <name since="">avg1() -> SystemLoad | {error, Reason}</name> <fsummary>Get the system load average for the last minute</fsummary> <type> <v>SystemLoad = int()</v> @@ -104,7 +104,7 @@ </desc> </func> <func> - <name>avg5() -> SystemLoad | {error, Reason}</name> + <name since="">avg5() -> SystemLoad | {error, Reason}</name> <fsummary>Get the system load average for the last five minutes</fsummary> <type> <v>SystemLoad = int()</v> @@ -118,7 +118,7 @@ </desc> </func> <func> - <name>avg15() -> SystemLoad | {error, Reason}</name> + <name since="">avg15() -> SystemLoad | {error, Reason}</name> <fsummary>Get the system load average for the last fifteen minutes</fsummary> <type> <v>SystemLoad = int()</v> @@ -132,7 +132,7 @@ </desc> </func> <func> - <name>util() -> CpuUtil | {error, Reason}</name> + <name since="">util() -> CpuUtil | {error, Reason}</name> <fsummary>Get the CPU utilization</fsummary> <type> <v>CpuUtil = float()</v> @@ -156,7 +156,7 @@ </desc> </func> <func> - <name>util(Opts) -> UtilSpec | {error, Reason}</name> + <name since="">util(Opts) -> UtilSpec | {error, Reason}</name> <fsummary>Get the CPU utilization</fsummary> <type> <v>Opts = [detailed | per_cpu]</v> diff --git a/lib/os_mon/doc/src/disksup.xml b/lib/os_mon/doc/src/disksup.xml index 610ef2c907..116a6dfd19 100644 --- a/lib/os_mon/doc/src/disksup.xml +++ b/lib/os_mon/doc/src/disksup.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>disksup</module> + <module since="">disksup</module> <modulesummary>A Disk Supervisor Process</modulesummary> <description> <p><c>disksup</c> is a process which supervises the available disk @@ -92,7 +92,7 @@ </section> <funcs> <func> - <name>get_disk_data() -> [DiskData]</name> + <name since="">get_disk_data() -> [DiskData]</name> <fsummary>Get data for the disks in the system</fsummary> <type> <v>DiskData = {Id, KByte, Capacity}</v> @@ -112,7 +112,7 @@ </desc> </func> <func> - <name>get_check_interval() -> MS</name> + <name since="">get_check_interval() -> MS</name> <fsummary>Get time interval, in milliseconds, for the periodic disk space check</fsummary> <type> <v>MS = int()</v> @@ -123,7 +123,7 @@ </desc> </func> <func> - <name>set_check_interval(Minutes) -> ok</name> + <name since="">set_check_interval(Minutes) -> ok</name> <fsummary>Set time interval, in minutes, for the periodic disk space check</fsummary> <type> <v>Minutes = int()>=1</v> @@ -138,7 +138,7 @@ </desc> </func> <func> - <name>get_almost_full_threshold() -> Percent</name> + <name since="">get_almost_full_threshold() -> Percent</name> <fsummary>Get threshold, in percent, for disk space utilization</fsummary> <type> <v>Percent = int()</v> @@ -148,7 +148,7 @@ </desc> </func> <func> - <name>set_almost_full_threshold(Float) -> ok</name> + <name since="">set_almost_full_threshold(Float) -> ok</name> <fsummary>Set threshold, as percentage represented by a float, for disk space utilization</fsummary> <type> <v>Float = float(), 0=<Float=<1</v> diff --git a/lib/os_mon/doc/src/memsup.xml b/lib/os_mon/doc/src/memsup.xml index c669e4670a..51c78b07c2 100644 --- a/lib/os_mon/doc/src/memsup.xml +++ b/lib/os_mon/doc/src/memsup.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>memsup</module> + <module since="">memsup</module> <modulesummary>A Memory Supervisor Process</modulesummary> <description> <p><c>memsup</c> is a process which supervises the memory usage for @@ -127,7 +127,7 @@ </section> <funcs> <func> - <name>get_memory_data() -> {Total,Allocated,Worst}</name> + <name since="">get_memory_data() -> {Total,Allocated,Worst}</name> <fsummary>Get data for the memory in the system</fsummary> <type> <v>Total = Allocated = int()</v> @@ -155,7 +155,7 @@ </desc> </func> <func> - <name>get_system_memory_data() -> MemDataList</name> + <name since="">get_system_memory_data() -> MemDataList</name> <fsummary>Get system dependent memory data</fsummary> <type> <v>MemDataList = [{Tag, Size}]</v> @@ -216,7 +216,7 @@ </desc> </func> <func> - <name>get_os_wordsize() -> Wordsize</name> + <name since="">get_os_wordsize() -> Wordsize</name> <fsummary>Get the wordsize of running os.</fsummary> <type> <v>Wordsize = 32 | 64 | unsupported_os</v> @@ -226,7 +226,7 @@ </desc> </func> <func> - <name>get_check_interval() -> MS</name> + <name since="">get_check_interval() -> MS</name> <fsummary>Get time interval, in milliseconds, for the periodic memory check</fsummary> <type> <v>MS = int()</v> @@ -237,7 +237,7 @@ </desc> </func> <func> - <name>set_check_interval(Minutes) -> ok</name> + <name since="">set_check_interval(Minutes) -> ok</name> <fsummary>Set time interval, in minutes, for the periodic memory check</fsummary> <type> <v>Minutes = int()>0</v> @@ -252,7 +252,7 @@ </desc> </func> <func> - <name>get_procmem_high_watermark() -> int()</name> + <name since="">get_procmem_high_watermark() -> int()</name> <fsummary>Get threshold, in percent, for process memory allocation</fsummary> <desc> <p>Returns the threshold, in percent, for process memory @@ -260,7 +260,7 @@ </desc> </func> <func> - <name>set_procmem_high_watermark(Float) -> ok</name> + <name since="">set_procmem_high_watermark(Float) -> ok</name> <fsummary>Set threshold, as percentage represented by a float, for process memory allocation</fsummary> <desc> <p>Changes the threshold, given as a float, for process memory @@ -273,7 +273,7 @@ </desc> </func> <func> - <name>get_sysmem_high_watermark() -> int()</name> + <name since="">get_sysmem_high_watermark() -> int()</name> <fsummary>Get threshold, in percent, for system memory allocation</fsummary> <desc> <p>Returns the threshold, in percent, for system memory @@ -281,7 +281,7 @@ </desc> </func> <func> - <name>set_sysmem_high_watermark(Float) -> ok</name> + <name since="">set_sysmem_high_watermark(Float) -> ok</name> <fsummary>Set threshold, given as a float, for system memory allocation</fsummary> <desc> <p>Changes the threshold, given as a float, for system memory @@ -294,7 +294,7 @@ </desc> </func> <func> - <name>get_helper_timeout() -> Seconds</name> + <name since="">get_helper_timeout() -> Seconds</name> <fsummary>Get the timeout value, in seconds, for memory checks</fsummary> <type> <v>Seconds = int()</v> @@ -304,7 +304,7 @@ </desc> </func> <func> - <name>set_helper_timeout(Seconds) -> ok</name> + <name since="">set_helper_timeout(Seconds) -> ok</name> <fsummary>Set the timeout value, in seconds, for memory checks</fsummary> <type> <v>Seconds = int() (>= 1)</v> diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml index 0910b3c0f3..64e9f281e3 100644 --- a/lib/os_mon/doc/src/notes.xml +++ b/lib/os_mon/doc/src/notes.xml @@ -31,6 +31,24 @@ </header> <p>This document describes the changes made to the OS_Mon application.</p> +<section><title>Os_Mon 2.4.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Due to <c>/proc</c> restrictions in newer Android + releases enforced by SELinux, cpu_sup is fixed so that it + gets some basic CPU stats using the <c>sysinfo</c> + syscall rather than reading <c>/proc/loadavg</c>.</p> + <p> + Own Id: OTP-15387 Aux Id: PR-1966 </p> + </item> + </list> + </section> + +</section> + <section><title>Os_Mon 2.4.6</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/os_mon/doc/src/nteventlog.xml b/lib/os_mon/doc/src/nteventlog.xml index d32427227c..08cf165a24 100644 --- a/lib/os_mon/doc/src/nteventlog.xml +++ b/lib/os_mon/doc/src/nteventlog.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>nteventlog</module> + <module since="">nteventlog</module> <modulesummary>Interface to Windows Event Log</modulesummary> <description> <p><c>nteventlog</c> provides a generic interface to the Windows @@ -61,8 +61,8 @@ </description> <funcs> <func> - <name>start(Identifier, MFA) -> Result</name> - <name>start_link(Identifier, MFA) -> Result</name> + <name since="">start(Identifier, MFA) -> Result</name> + <name since="">start_link(Identifier, MFA) -> Result</name> <fsummary>Start the NT eventlog server</fsummary> <type> <v>Identifier = string() | atom()</v> @@ -82,7 +82,7 @@ </desc> </func> <func> - <name>stop() -> stopped</name> + <name since="">stop() -> stopped</name> <fsummary>Stop the NT eventlog server</fsummary> <type> <v>Result = stopped</v> diff --git a/lib/os_mon/doc/src/os_sup.xml b/lib/os_mon/doc/src/os_sup.xml index d517f387b4..4a84165a6c 100644 --- a/lib/os_mon/doc/src/os_sup.xml +++ b/lib/os_mon/doc/src/os_sup.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>os_sup</module> + <module since="">os_sup</module> <modulesummary>Interface to OS System Messages</modulesummary> <description> <p><c>os_sup</c> is a process providing a message passing service @@ -159,8 +159,8 @@ </section> <funcs> <func> - <name>enable() -> ok | {error, Res}</name> - <name>enable(Dir, Conf) -> ok | {error, Error}</name> + <name since="">enable() -> ok | {error, Res}</name> + <name since="">enable(Dir, Conf) -> ok | {error, Error}</name> <fsummary>Enable the service (Solaris only)</fsummary> <type> <v>Dir = Conf = Res = string()</v> @@ -194,8 +194,8 @@ </desc> </func> <func> - <name>disable() -> ok | {error, Res}</name> - <name>disable(Dir, Conf) -> ok | {error, Error}</name> + <name since="">disable() -> ok | {error, Res}</name> + <name since="">disable(Dir, Conf) -> ok | {error, Error}</name> <fsummary>Disable the service (Solaris only)</fsummary> <type> <v>Dir = Conf = Res = string()</v> diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk index 0c62c3db35..9713f6bc6b 100644 --- a/lib/os_mon/vsn.mk +++ b/lib/os_mon/vsn.mk @@ -1 +1 @@ -OS_MON_VSN = 2.4.6 +OS_MON_VSN = 2.4.7 diff --git a/lib/parsetools/doc/src/leex.xml b/lib/parsetools/doc/src/leex.xml index 1227625287..3b82f60201 100644 --- a/lib/parsetools/doc/src/leex.xml +++ b/lib/parsetools/doc/src/leex.xml @@ -21,7 +21,7 @@ <rev>A</rev> <file>leex.xml</file> </header> - <module>leex</module> + <module since="">leex</module> <modulesummary>Lexical analyzer generator for Erlang</modulesummary> <description> <p>A regular expression based lexical analyzer generator for @@ -38,7 +38,8 @@ Token = tuple()</code> </section> <funcs> <func> - <name>file(FileName, [, Options]) -> LeexRet</name> + <name since="">file(FileName) -> LeexRet</name> + <name since="OTP R16B02">file(FileName, Options) -> LeexRet</name> <fsummary>Generate a lexical analyzer</fsummary> <type> <v>FileName = filename()</v> @@ -124,7 +125,7 @@ Token = tuple()</code> </desc> </func> <func> - <name>format_error(ErrorInfo) -> Chars</name> + <name since="">format_error(ErrorInfo) -> Chars</name> <fsummary>Return an English description of a an error tuple.</fsummary> <type> <v>Chars = [char() | Chars]</v> @@ -145,8 +146,8 @@ Token = tuple()</code> <funcs> <func> - <name>string(String) -> StringRet</name> - <name>string(String, StartLine) -> StringRet</name> + <name since="">string(String) -> StringRet</name> + <name since="">string(String, StartLine) -> StringRet</name> <fsummary>Generated by Leex</fsummary> <type> <v>String = string()</v> @@ -163,9 +164,9 @@ Token = tuple()</code> </func> <func> - <name>token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars} + <name since="">token(Cont, Chars) -> {more,Cont1} | {done,TokenRet,RestChars} </name> - <name>token(Cont, Chars, StartLine) -> {more,Cont1} + <name since="">token(Cont, Chars, StartLine) -> {more,Cont1} | {done,TokenRet,RestChars} </name> <fsummary>Generated by Leex</fsummary> @@ -198,9 +199,9 @@ io:request(InFile, {get_until,Prompt,Module,token,[Line]}) </func> <func> - <name>tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars} + <name since="">tokens(Cont, Chars) -> {more,Cont1} | {done,TokensRet,RestChars} </name> - <name>tokens(Cont, Chars, StartLine) -> + <name since="">tokens(Cont, Chars, StartLine) -> {more,Cont1} | {done,TokensRet,RestChars} </name> <fsummary>Generated by Leex</fsummary> diff --git a/lib/parsetools/doc/src/yecc.xml b/lib/parsetools/doc/src/yecc.xml index 5f95b5c150..67a2c95c25 100644 --- a/lib/parsetools/doc/src/yecc.xml +++ b/lib/parsetools/doc/src/yecc.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>yecc.sgml</file> </header> - <module>yecc</module> + <module since="">yecc</module> <modulesummary>LALR-1 Parser Generator</modulesummary> <description> <p>An LALR-1 parser generator for Erlang, similar to <c>yacc</c>. @@ -46,7 +46,7 @@ </description> <funcs> <func> - <name>file(Grammarfile [, Options]) -> YeccRet</name> + <name since="">file(Grammarfile [, Options]) -> YeccRet</name> <fsummary>Give information about resolved and unresolved parse action conflicts.</fsummary> <type> <v>Grammarfile = filename()</v> @@ -137,7 +137,7 @@ </desc> </func> <func> - <name>format_error(Reason) -> Chars</name> + <name since="">format_error(Reason) -> Chars</name> <fsummary>Return an English description of a an error tuple.</fsummary> <type> <v>Reason = - as returned by yecc:file/1,2 -</v> diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml index 7ed60ed3ca..b3e6023c41 100644 --- a/lib/public_key/doc/src/notes.xml +++ b/lib/public_key/doc/src/notes.xml @@ -35,6 +35,24 @@ <file>notes.xml</file> </header> +<section><title>Public_Key 1.6.4</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Added ed25519 and ed448 sign/verify.</p> + <p> + Requires OpenSSL 1.1.1 or higher as cryptolib under the + OTP application <c>crypto</c>.</p> + <p> + Own Id: OTP-15419 Aux Id: OTP-15094 </p> + </item> + </list> + </section> + +</section> + <section><title>Public_Key 1.6.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 9523a3b7c2..4c61139197 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -31,7 +31,7 @@ <date></date> <rev></rev> </header> - <module>public_key</module> + <module since="">public_key</module> <modulesummary>API module for public-key infrastructure.</modulesummary> <description> <p>Provides functions to handle public-key infrastructure, @@ -176,7 +176,7 @@ <funcs> <func> - <name name="compute_key" arity="2"/> + <name name="compute_key" arity="2" since="OTP R16B01"/> <fsummary>Computes shared secret.</fsummary> <desc> <p>Computes shared secret.</p> @@ -184,7 +184,7 @@ </func> <func> - <name name="compute_key" arity="3"/> + <name name="compute_key" arity="3" since="OTP R16B01"/> <fsummary>Computes shared secret.</fsummary> <desc> <p>Computes shared secret.</p> @@ -192,8 +192,8 @@ </func> <func> - <name name="decrypt_private" arity="2"/> - <name name="decrypt_private" arity="3"/> + <name name="decrypt_private" arity="2" since="OTP R14B"/> + <name name="decrypt_private" arity="3" since="OTP R14B"/> <fsummary>Public-key decryption.</fsummary> <desc> <p>Public-key decryption using the private key. See also <seealso @@ -202,8 +202,8 @@ </func> <func> - <name name="decrypt_public" arity="2"/> - <name name="decrypt_public" arity="3"/> + <name name="decrypt_public" arity="2" since="OTP R14B"/> + <name name="decrypt_public" arity="3" since="OTP R14B"/> <fsummary>Public-key decryption.</fsummary> <desc> <p>Public-key decryption using the public key. See also <seealso @@ -212,7 +212,7 @@ </func> <func> - <name name="der_decode" arity="2"/> + <name name="der_decode" arity="2" since="OTP R14B"/> <fsummary>Decodes a public-key ASN.1 DER encoded entity.</fsummary> <desc> <p>Decodes a public-key ASN.1 DER encoded entity.</p> @@ -220,7 +220,7 @@ </func> <func> - <name name="der_encode" arity="2"/> + <name name="der_encode" arity="2" since="OTP R14B"/> <fsummary>Encodes a public-key entity with ASN.1 DER encoding.</fsummary> <desc> <p>Encodes a public-key entity with ASN.1 DER encoding.</p> @@ -228,7 +228,7 @@ </func> <func> - <name name="dh_gex_group" arity="4"/> + <name name="dh_gex_group" arity="4" since="OTP 18.2"/> <fsummary>Selects a group for Diffie-Hellman key exchange</fsummary> <desc> <p>Selects a group for Diffie-Hellman key exchange with the key size in the range <c>MinSize...MaxSize</c> @@ -249,8 +249,8 @@ </func> <func> - <name name="encrypt_private" arity="2"/> - <name name="encrypt_private" arity="3"/> + <name name="encrypt_private" arity="2" since="OTP R14B"/> + <name name="encrypt_private" arity="3" since="OTP 21.1"/> <fsummary>Public-key encryption using the private key.</fsummary> <desc> <p>Public-key encryption using the private key. @@ -260,8 +260,8 @@ </func> <func> - <name name="encrypt_public" arity="2"/> - <name name="encrypt_public" arity="3"/> + <name name="encrypt_public" arity="2" since="OTP R14B"/> + <name name="encrypt_public" arity="3" since="OTP 21.1"/> <fsummary>Public-key encryption using the public key.</fsummary> <desc> <p>Public-key encryption using the public key. See also <seealso @@ -270,7 +270,7 @@ </func> <func> - <name name="generate_key" arity="1"/> + <name name="generate_key" arity="1" since="OTP R16B01"/> <fsummary>Generates a new keypair.</fsummary> <desc> <p>Generates a new keypair. Note that except for Diffie-Hellman @@ -281,7 +281,7 @@ </func> <func> - <name name="pem_decode" arity="1"/> + <name name="pem_decode" arity="1" since="OTP R14B"/> <fsummary>Decodes PEM binary data and returns entries as ASN.1 DER encoded entities.</fsummary> <desc> @@ -291,7 +291,7 @@ </func> <func> - <name name="pem_encode" arity="1"/> + <name name="pem_encode" arity="1" since="OTP R14B"/> <fsummary>Creates a PEM binary.</fsummary> <desc> <p>Creates a PEM binary.</p> @@ -299,8 +299,8 @@ </func> <func> - <name name="pem_entry_decode" arity="1"/> - <name name="pem_entry_decode" arity="2"/> + <name name="pem_entry_decode" arity="1" since="OTP R14B"/> + <name name="pem_entry_decode" arity="2" since="OTP R14B"/> <fsummary>Decodes a PEM entry.</fsummary> <desc> <p>Decodes a PEM entry. <c>pem_decode/1</c> returns a list of PEM @@ -311,8 +311,8 @@ </func> <func> - <name name="pem_entry_encode" arity="2"/> - <name name="pem_entry_encode" arity="3"/> + <name name="pem_entry_encode" arity="2" since="OTP R14B"/> + <name name="pem_entry_encode" arity="3" since="OTP R14B"/> <fsummary>Creates a PEM entry that can be fed to <c>pem_encode/1</c>.</fsummary> <desc> <p>Creates a PEM entry that can be feed to <c>pem_encode/1</c>.</p> @@ -326,7 +326,7 @@ </func> <func> - <name name="pkix_decode_cert" arity="2"/> + <name name="pkix_decode_cert" arity="2" since=""/> <fsummary>Decodes an ASN.1 DER-encoded PKIX x509 certificate.</fsummary> <desc> <p>Decodes an ASN.1 DER-encoded PKIX certificate. Option <c>otp</c> @@ -337,7 +337,7 @@ </func> <func> - <name name="pkix_encode" arity="3"/> + <name name="pkix_encode" arity="3" since="OTP R14B"/> <fsummary>DER encodes a PKIX x509 certificate or part of such a certificate.</fsummary> <desc> @@ -349,7 +349,7 @@ </func> <func> - <name name="pkix_is_issuer" arity="2"/> + <name name="pkix_is_issuer" arity="2" since="OTP R14B"/> <fsummary>Checks if <c>IssuerCert</c> issued <c>Cert</c>.</fsummary> <desc> <p>Checks if <c>IssuerCert</c> issued <c>Cert</c>.</p> @@ -357,7 +357,7 @@ </func> <func> - <name name="pkix_is_fixed_dh_cert" arity="1"/> + <name name="pkix_is_fixed_dh_cert" arity="1" since="OTP R14B"/> <fsummary>Checks if a certificate is a fixed Diffie-Hellman certificate.</fsummary> <desc> <p>Checks if a certificate is a fixed Diffie-Hellman certificate.</p> @@ -365,7 +365,7 @@ </func> <func> - <name name="pkix_is_self_signed" arity="1"/> + <name name="pkix_is_self_signed" arity="1" since="OTP R14B"/> <fsummary>Checks if a certificate is self-signed.</fsummary> <desc> <p>Checks if a certificate is self-signed.</p> @@ -373,7 +373,7 @@ </func> <func> - <name name="pkix_issuer_id" arity="2"/> + <name name="pkix_issuer_id" arity="2" since="OTP R14B"/> <fsummary>Returns the issuer id.</fsummary> <desc> <p>Returns the issuer id.</p> @@ -381,7 +381,7 @@ </func> <func> - <name name="pkix_normalize_name" arity="1"/> + <name name="pkix_normalize_name" arity="1" since="OTP R14B"/> <fsummary>Normalizes an issuer name so that it can be easily compared to another issuer name.</fsummary> <desc> @@ -391,7 +391,7 @@ </func> <func> - <name>pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name> + <name since="OTP R16B">pkix_path_validation(TrustedCert, CertChain, Options) -> {ok, {PublicKeyInfo, PolicyTree}} | {error, {bad_cert, Reason}} </name> <fsummary>Performs a basic path validation according to RFC 5280.</fsummary> <type> <v>TrustedCert = #'OTPCertificate'{} | der_encoded() | atom()</v> @@ -491,7 +491,7 @@ fun(OtpCert :: #'OTPCertificate'{}, </func> <func> - <name name="pkix_crl_issuer" arity="1"/> + <name name="pkix_crl_issuer" arity="1" since="OTP 17.5"/> <fsummary>Returns the issuer of the <c>CRL</c>.</fsummary> <desc> <p>Returns the issuer of the <c>CRL</c>.</p> @@ -499,7 +499,7 @@ fun(OtpCert :: #'OTPCertificate'{}, </func> <func> - <name name="pkix_crls_validate" arity="3"/> + <name name="pkix_crls_validate" arity="3" since="OTP R16B"/> <fsummary>Performs CRL validation.</fsummary> <desc> <p>Performs CRL validation. It is intended to be called from @@ -551,7 +551,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_crl_verify" arity="2"/> + <name name="pkix_crl_verify" arity="2" since="OTP 17.5"/> <fsummary> Verify that <c>Cert</c> is the <c> CRL</c> signer. </fsummary> <desc> <p>Verify that <c>Cert</c> is the <c>CRL</c> signer.</p> @@ -559,7 +559,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_dist_point" arity="1"/> + <name name="pkix_dist_point" arity="1" since="OTP 17.5"/> <fsummary>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>.</fsummary> <desc> <p>Creates a distribution point for CRLs issued by the same issuer as <c>Cert</c>. @@ -570,7 +570,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_dist_points" arity="1"/> + <name name="pkix_dist_points" arity="1" since="OTP 17.5"/> <fsummary> Extracts distribution points from the certificates extensions.</fsummary> <desc> <p> Extracts distribution points from the certificates extensions.</p> @@ -578,7 +578,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_match_dist_point" arity="2"/> + <name name="pkix_match_dist_point" arity="2" since="OTP 19.0"/> <fsummary>Checks whether the given distribution point matches the Issuing Distribution Point of the CRL.</fsummary> <desc> @@ -590,7 +590,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_sign" arity="2"/> + <name name="pkix_sign" arity="2" since="OTP R14B"/> <fsummary>Signs certificate.</fsummary> <desc> <p>Signs an 'OTPTBSCertificate'. Returns the corresponding @@ -599,7 +599,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_sign_types" arity="1"/> + <name name="pkix_sign_types" arity="1" since="OTP R16B01"/> <fsummary>Translates signature algorithm OID to Erlang digest and signature algorithm types.</fsummary> <desc> <p>Translates signature algorithm OID to Erlang digest and signature types. @@ -609,8 +609,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name>pkix_test_data(Options) -> Config </name> - <name>pkix_test_data([chain_opts()]) -> [conf_opt()]</name> + <name since="OTP 20.1">pkix_test_data(Options) -> Config </name> + <name since="OTP 20.1">pkix_test_data([chain_opts()]) -> [conf_opt()]</name> <fsummary>Creates certificate test data.</fsummary> <type> <v>Options = #{chain_type() := chain_opts()} </v> @@ -742,7 +742,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name>pkix_test_root_cert(Name, Options) -> RootCert</name> + <name since="OTP 20.2">pkix_test_root_cert(Name, Options) -> RootCert</name> <fsummary>Generates a test data root cert.</fsummary> <type> <v>Name = string()</v> @@ -772,7 +772,7 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name name="pkix_verify" arity="2"/> + <name name="pkix_verify" arity="2" since="OTP R14B"/> <fsummary>Verifies PKIX x.509 certificate signature.</fsummary> <desc> <p>Verifies PKIX x.509 certificate signature.</p> @@ -780,8 +780,8 @@ fun(#'DistributionPoint'{}, #'CertificateList'{}, </func> <func> - <name>pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()</name> - <name>pkix_verify_hostname(Cert, ReferenceIDs, Opts) -> boolean()</name> + <name since="OTP 19.3">pkix_verify_hostname(Cert, ReferenceIDs) -> boolean()</name> + <name since="OTP 19.3">pkix_verify_hostname(Cert, ReferenceIDs, Opts) -> boolean()</name> <fsummary>Verifies that a PKIX x.509 certificate <i>presented identifier</i> (e.g hostname) is an expected one.</fsummary> <type> @@ -864,7 +864,7 @@ end </func> <func> - <name>pkix_verify_hostname_match_fun(Protcol) -> fun(RefId | FQDN::string(), PresentedID) -> boolean() | default</name> + <name since="OTP 21.0">pkix_verify_hostname_match_fun(Protcol) -> fun(RefId | FQDN::string(), PresentedID) -> boolean() | default</name> <fsummary>Returns a fun that is intendended as argument to the match_fun option in pkix_verify_hostname/3. </fsummary> <type> @@ -889,8 +889,8 @@ end <func> - <name name="sign" arity="3"/> - <name name="sign" arity="4"/> + <name name="sign" arity="3" since=""/> + <name name="sign" arity="4" since="OTP 20.1"/> <fsummary>Creates a digital signature.</fsummary> <desc> <p>Creates a digital signature.</p> @@ -901,7 +901,7 @@ end </func> <func> - <name name="ssh_decode" arity="2"/> + <name name="ssh_decode" arity="2" since="OTP R14B03"/> <fsummary>Decodes an SSH file-binary.</fsummary> <desc> <p>Decodes an SSH file-binary. In the case of <c>known_hosts</c> or @@ -933,7 +933,7 @@ end </func> <func> - <name name="ssh_encode" arity="2"/> + <name name="ssh_encode" arity="2" since="OTP R14B03"/> <fsummary>Encodes a list of SSH file entries to a binary.</fsummary> <desc> <p>Encodes a list of SSH file entries (public keys and attributes) to a binary. Possible @@ -947,9 +947,9 @@ end </func> <func> - <name>ssh_hostkey_fingerprint(HostKey) -> string()</name> - <name>ssh_hostkey_fingerprint(DigestType, HostKey) -> string()</name> - <name>ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name> + <name since="OTP 19.2">ssh_hostkey_fingerprint(HostKey) -> string()</name> + <name since="OTP 19.2">ssh_hostkey_fingerprint(DigestType, HostKey) -> string()</name> + <name since="OTP 19.2">ssh_hostkey_fingerprint([DigestType], HostKey) -> [string()]</name> <fsummary>Calculates a ssh fingerprint for a hostkey.</fsummary> <type> <v>HostKey = <seealso marker="#type-public_key">public_key()</seealso></v> @@ -982,8 +982,8 @@ end </func> <func> - <name name="verify" arity="4"/> - <name name="verify" arity="5"/> + <name name="verify" arity="4" since="OTP R14B"/> + <name name="verify" arity="5" since="OTP 20.1"/> <fsummary>Verifies a digital signature.</fsummary> <desc> <p>Verifies a digital signature.</p> @@ -993,7 +993,7 @@ end </func> <func> - <name name="short_name_hash" arity="1"/> + <name name="short_name_hash" arity="1" since="OTP 19.0"/> <fsummary>Generates a short hash of an issuer name.</fsummary> <desc> <p>Generates a short hash of an issuer name. The hash is diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk index 96eaf4f962..5e2643f0ea 100644 --- a/lib/public_key/vsn.mk +++ b/lib/public_key/vsn.mk @@ -1 +1 @@ -PUBLIC_KEY_VSN = 1.6.3 +PUBLIC_KEY_VSN = 1.6.4 diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml index e201ad4e23..165ae6db6a 100644 --- a/lib/reltool/doc/src/notes.xml +++ b/lib/reltool/doc/src/notes.xml @@ -38,7 +38,25 @@ thus constitutes one section in this document. The title of each section is the version number of Reltool.</p> - <section><title>Reltool 0.7.7</title> + <section><title>Reltool 0.7.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Reltool would earlier erroneously split paths like + <c>"c:\foo"</c> into <c>["c","\foo"]</c> when reading the + <c>$ERL_LIBS</c> variable on windows. This is now + corrected.</p> + <p> + Own Id: OTP-15454</p> + </item> + </list> + </section> + +</section> + +<section><title>Reltool 0.7.7</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml index 874cda8369..5afbad0ba8 100644 --- a/lib/reltool/doc/src/reltool.xml +++ b/lib/reltool/doc/src/reltool.xml @@ -33,7 +33,7 @@ <date></date> <rev>%VSN%</rev> </header> - <module>reltool</module> + <module since="">reltool</module> <modulesummary>Main API of the Reltool application</modulesummary> <description> <p>This is an interface module for the Reltool application.</p> @@ -591,7 +591,7 @@ target_spec() = [target_spec()] <funcs> <func> - <name>create_target(Server, TargetDir) -> ok | {error, Reason}</name> + <name since="">create_target(Server, TargetDir) -> ok | {error, Reason}</name> <fsummary>Create a target system</fsummary> <type> <v>Server = server()</v> @@ -604,7 +604,7 @@ target_spec() = [target_spec()] </func> <func> - <name>eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name> + <name since="">eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name> <fsummary>Create a target system</fsummary> <type> <v>TargetSpec = target_spec()</v> @@ -655,7 +655,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_config(Server) -> {ok, Config} | {error, Reason}</name> + <name since="">get_config(Server) -> {ok, Config} | {error, Reason}</name> <fsummary>Get reltool configuration</fsummary> <type> <v>Server = server()</v> @@ -667,7 +667,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name> + <name since="">get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name> <fsummary>Get reltool configuration</fsummary> <type> <v>Server = server()</v> @@ -685,7 +685,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name> + <name since="">get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name> <fsummary>Get contents of a release file</fsummary> <type> <v>Server = server()</v> @@ -698,7 +698,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name> + <name since="">get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name> <fsummary>Get contents of a boot script file</fsummary> <type> <v>Server = server()</v> @@ -711,7 +711,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_status(Server) -> {ok, [Warning]} | {error, Reason}</name> + <name since="OTP R14B">get_status(Server) -> {ok, [Warning]} | {error, Reason}</name> <fsummary>Get contents of a release file</fsummary> <type> <v>Server = server()</v> @@ -722,7 +722,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name> + <name since="">get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name> <fsummary>Start server process with options</fsummary> <type> <v>WindowPid = window_pid()</v> @@ -733,7 +733,7 @@ target_spec() = [target_spec()] </func> <func> - <name>get_target_spec(Server) -> {ok, TargetSpec} | {error, Reason}</name> + <name since="">get_target_spec(Server) -> {ok, TargetSpec} | {error, Reason}</name> <fsummary>Return a specification of the target system</fsummary> <type> <v>Server = server()</v> @@ -747,7 +747,7 @@ target_spec() = [target_spec()] </func> <func> - <name>install(RelName, TargetDir) -> ok | {error, Reason}</name> + <name since="">install(RelName, TargetDir) -> ok | {error, Reason}</name> <fsummary>Install a target system</fsummary> <type> <v>RelName = rel_name()</v> @@ -758,7 +758,7 @@ target_spec() = [target_spec()] </func> <func> - <name>start() -> {ok, WindowPid} | {error, Reason}</name> + <name since="">start() -> {ok, WindowPid} | {error, Reason}</name> <fsummary>Start main window process</fsummary> <type> <v>WindowPid = window_pid()</v> @@ -768,7 +768,7 @@ target_spec() = [target_spec()] </func> <func> - <name>start(Options) -> {ok, WindowPid} | {error, Reason}</name> + <name since="">start(Options) -> {ok, WindowPid} | {error, Reason}</name> <fsummary>Start main window process with options</fsummary> <type> <v>Options = options()</v> @@ -779,7 +779,7 @@ target_spec() = [target_spec()] </func> <func> - <name>start_link(Options) -> {ok, WindowPid} | {error, Reason}</name> + <name since="">start_link(Options) -> {ok, WindowPid} | {error, Reason}</name> <fsummary>Start main window process with options</fsummary> <type> <v>Options = options()</v> @@ -790,7 +790,7 @@ target_spec() = [target_spec()] </func> <func> - <name>start_server(Options) -> {ok, ServerPid} | {error, Reason}</name> + <name since="">start_server(Options) -> {ok, ServerPid} | {error, Reason}</name> <fsummary>Start server process with options</fsummary> <type> <v>Options = options()</v> @@ -803,7 +803,7 @@ target_spec() = [target_spec()] </func> <func> - <name>stop(Pid) -> ok | {error, Reason}</name> + <name since="">stop(Pid) -> ok | {error, Reason}</name> <fsummary>Stop a server or window process</fsummary> <type> <v>Pid = server_pid() | window_pid()</v> diff --git a/lib/reltool/src/reltool_utils.erl b/lib/reltool/src/reltool_utils.erl index 060a0912f9..2afa386cb3 100644 --- a/lib/reltool/src/reltool_utils.erl +++ b/lib/reltool/src/reltool_utils.erl @@ -47,6 +47,9 @@ call/2, cast/2, reply/3]). +%% For testing +-export([erl_libs/2]). + -include_lib("kernel/include/file.hrl"). -include_lib("wx/include/wx.hrl"). -include("reltool.hrl"). @@ -55,7 +58,15 @@ root_dir() -> code:root_dir(). erl_libs() -> - string:lexemes(os:getenv("ERL_LIBS", ""), ":;"). + erl_libs(os:getenv("ERL_LIBS", ""), os:type()). + +erl_libs(ErlLibs, OsType) when is_list(ErlLibs) -> + Sep = + case OsType of + {win32, _} -> ";"; + _ -> ":" + end, + string:lexemes(ErlLibs, Sep). lib_dirs(Dir) -> case erl_prim_loader:list_dir(Dir) of diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl index fc976bfb94..990bd5c217 100644 --- a/lib/reltool/test/reltool_server_SUITE.erl +++ b/lib/reltool/test/reltool_server_SUITE.erl @@ -142,7 +142,8 @@ all() -> use_selected_vsn, use_selected_vsn_relative_path, non_standard_vsn_id, - undefined_regexp]. + undefined_regexp, + windows_erl_libs]. groups() -> []. @@ -2546,10 +2547,21 @@ undefined_regexp(_Config) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Checks that reltool_utils can correctly read Windows ERL_LIBS + +windows_erl_libs(_Config) -> + WinErlLibs = + "C:\\Program Files\\Erlang Libs;C:\\Program Files\\More Erlang Libs", + Ret = reltool_utils:erl_libs(WinErlLibs, {win32, nt}), + ?m(["C:\\Program Files\\Erlang Libs","C:\\Program Files\\More Erlang Libs"], + Ret), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Library functions erl_libs() -> - string:lexemes(os:getenv("ERL_LIBS", ""), ":;"). + reltool_utils:erl_libs(). datadir(Config) -> %% Removes the trailing slash... diff --git a/lib/reltool/vsn.mk b/lib/reltool/vsn.mk index 6bfc8c60ea..a649a3e0c0 100644 --- a/lib/reltool/vsn.mk +++ b/lib/reltool/vsn.mk @@ -1 +1 @@ -RELTOOL_VSN = 0.7.7 +RELTOOL_VSN = 0.7.8 diff --git a/lib/runtime_tools/doc/src/dbg.xml b/lib/runtime_tools/doc/src/dbg.xml index ae60b610ed..e15fc3efe6 100644 --- a/lib/runtime_tools/doc/src/dbg.xml +++ b/lib/runtime_tools/doc/src/dbg.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>dbg.sgml</file> </header> - <module>dbg</module> + <module since="">dbg</module> <modulesummary>The Text Based Trace Facility</modulesummary> <description> <p>This module implements a text based interface to the @@ -68,7 +68,7 @@ </description> <funcs> <func> - <name>fun2ms(LiteralFun) -> MatchSpec</name> + <name since="">fun2ms(LiteralFun) -> MatchSpec</name> <fsummary>Pseudo function that transforms fun syntax to match_spec.</fsummary> <type> <v>LiteralFun = fun() literal</v> @@ -145,14 +145,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>h() -> ok </name> + <name since="">h() -> ok </name> <fsummary>Give a list of available help items on standard output.</fsummary> <desc> <p>Gives a list of items for brief online help.</p> </desc> </func> <func> - <name>h(Item) -> ok </name> + <name since="">h(Item) -> ok </name> <fsummary>Give brief help for an item.</fsummary> <type> <v>Item = atom()</v> @@ -163,14 +163,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>p(Item) -> {ok, MatchDesc} | {error, term()} </name> + <name since="">p(Item) -> {ok, MatchDesc} | {error, term()} </name> <fsummary>Trace messages to and from Item.</fsummary> <desc> <p>Equivalent to <c>p(Item, [m])</c>.</p> </desc> </func> <func> - <name>p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name> + <name since="">p(Item, Flags) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Trace Item according to Flags.</fsummary> <type> <v>MatchDesc = [MatchNum]</v> @@ -303,14 +303,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>c(Mod, Fun, Args)</name> + <name since="">c(Mod, Fun, Args)</name> <fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>all</c>trace flags set.</fsummary> <desc> <p>Equivalent to <c>c(Mod, Fun, Args, all)</c>.</p> </desc> </func> <func> - <name>c(Mod, Fun, Args, Flags)</name> + <name since="">c(Mod, Fun, Args, Flags)</name> <fsummary>Evaluate <c>apply(M,F,Args)</c>with <c>Flags</c>trace flags set.</fsummary> <desc> <p>Evaluates the expression <c>apply(Mod, Fun, Args)</c> with the trace @@ -319,35 +319,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>i() -> ok</name> + <name since="">i() -> ok</name> <fsummary>Display information about all traced processes and ports.</fsummary> <desc> <p>Displays information about all traced processes and ports.</p> </desc> </func> <func> - <name>tp(Module,MatchSpec)</name> + <name since="">tp(Module,MatchSpec)</name> <fsummary>Set pattern for traced global function calls</fsummary> <desc> <p>Same as tp({Module, '_', '_'}, MatchSpec)</p> </desc> </func> <func> - <name>tp(Module,Function,MatchSpec)</name> + <name since="">tp(Module,Function,MatchSpec)</name> <fsummary>Set pattern for traced global function calls</fsummary> <desc> <p>Same as tp({Module, Function, '_'}, MatchSpec)</p> </desc> </func> <func> - <name>tp(Module, Function, Arity, MatchSpec)</name> + <name since="">tp(Module, Function, Arity, MatchSpec)</name> <fsummary>Set pattern for traced global function calls</fsummary> <desc> <p>Same as tp({Module, Function, Arity}, MatchSpec)</p> </desc> </func> <func> - <name>tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name> + <name since="">tp({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Set pattern for traced global function calls</fsummary> <type> <v>Module = atom() | '_'</v> @@ -410,28 +410,28 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>tpl(Module,MatchSpec)</name> + <name since="">tpl(Module,MatchSpec)</name> <fsummary>Set pattern for traced local (as well as global) function calls</fsummary> <desc> <p>Same as tpl({Module, '_', '_'}, MatchSpec)</p> </desc> </func> <func> - <name>tpl(Module,Function,MatchSpec)</name> + <name since="">tpl(Module,Function,MatchSpec)</name> <fsummary>Set pattern for traced local (as well as global) function calls</fsummary> <desc> <p>Same as tpl({Module, Function, '_'}, MatchSpec)</p> </desc> </func> <func> - <name>tpl(Module, Function, Arity, MatchSpec)</name> + <name since="">tpl(Module, Function, Arity, MatchSpec)</name> <fsummary>Set pattern for traced local (as well as global) function calls</fsummary> <desc> <p>Same as tpl({Module, Function, Arity}, MatchSpec)</p> </desc> </func> <func> - <name>tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name> + <name since="">tpl({Module, Function, Arity}, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Set pattern for traced local (as well as global) function calls</fsummary> <desc> <p>This function works as <seealso marker="#tp-2"><c>tp/2</c></seealso>, but enables @@ -442,7 +442,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ <func> - <name>tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name> + <name since="OTP 19.0">tpe(Event, MatchSpec) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Set pattern for traced event</fsummary> <type> <v>Event = send | 'receive'</v> @@ -484,35 +484,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>ctp()</name> + <name since="">ctp()</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctp({'_', '_', '_'})</p> </desc> </func> <func> - <name>ctp(Module)</name> + <name since="">ctp(Module)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctp({Module, '_', '_'})</p> </desc> </func> <func> - <name>ctp(Module, Function)</name> + <name since="">ctp(Module, Function)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctp({Module, Function, '_'})</p> </desc> </func> <func> - <name>ctp(Module, Function, Arity)</name> + <name since="">ctp(Module, Function, Arity)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctp({Module, Function, Arity})</p> </desc> </func> <func> - <name>ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name> + <name since="">ctp({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <type> <v>Module = atom() | '_'</v> @@ -533,35 +533,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>ctpl()</name> + <name since="">ctpl()</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpl({'_', '_', '_'})</p> </desc> </func> <func> - <name>ctpl(Module)</name> + <name since="">ctpl(Module)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpl({Module, '_', '_'})</p> </desc> </func> <func> - <name>ctpl(Module, Function)</name> + <name since="">ctpl(Module, Function)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpl({Module, Function, '_'})</p> </desc> </func> <func> - <name>ctpl(Module, Function, Arity)</name> + <name since="">ctpl(Module, Function, Arity)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpl({Module, Function, Arity})</p> </desc> </func> <func> - <name>ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name> + <name since="">ctpl({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables @@ -570,35 +570,35 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>ctpg()</name> + <name since="">ctpg()</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpg({'_', '_', '_'})</p> </desc> </func> <func> - <name>ctpg(Module)</name> + <name since="">ctpg(Module)</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpg({Module, '_', '_'})</p> </desc> </func> <func> - <name>ctpg(Module, Function)</name> + <name since="">ctpg(Module, Function)</name> <fsummary>>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpg({Module, Function, '_'})</p> </desc> </func> <func> - <name>ctpg(Module, Function, Arity)</name> + <name since="">ctpg(Module, Function, Arity)</name> <fsummary>>Clear call trace pattern for the specified functions</fsummary> <desc> <p>Same as ctpg({Module, Function, Arity})</p> </desc> </func> <func> - <name>ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name> + <name since="">ctpg({Module, Function, Arity}) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Clear call trace pattern for the specified functions</fsummary> <desc> <p>This function works as <seealso marker="#ctp-1"><c>ctp/1</c></seealso>, but only disables @@ -607,7 +607,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>ctpe(Event) -> {ok, MatchDesc} | {error, term()}</name> + <name since="OTP 19.0">ctpe(Event) -> {ok, MatchDesc} | {error, term()}</name> <fsummary>Clear trace pattern for the specified event</fsummary> <type> <v>Event = send | 'receive'</v> @@ -623,7 +623,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>ltp() -> ok</name> + <name since="">ltp() -> ok</name> <fsummary>List saved and built-in match specifications on the console.</fsummary> <desc> <p>Use this function to recall all match specifications previously @@ -654,7 +654,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>dtp() -> ok</name> + <name since="">dtp() -> ok</name> <fsummary>Delete all saved match specifications.</fsummary> <desc> <p>Use this function to "forget" all match specifications @@ -665,7 +665,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>dtp(N) -> ok</name> + <name since="">dtp(N) -> ok</name> <fsummary>Delete a specific saved match_spec.</fsummary> <type> <v>N = integer()</v> @@ -676,7 +676,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>wtp(Name) -> ok | {error, IOError}</name> + <name since="">wtp(Name) -> ok | {error, IOError}</name> <fsummary>Write all saved and built-in match specifications to a file</fsummary> <type> <v>Name = string()</v> @@ -699,7 +699,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>rtp(Name) -> ok | {error, Error}</name> + <name since="">rtp(Name) -> ok | {error, Error}</name> <fsummary>Read saved match specifications from file.</fsummary> <type> <v>Name = string()</v> @@ -728,7 +728,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>n(Nodename) -> {ok, Nodename} | {error, Reason}</name> + <name since="">n(Nodename) -> {ok, Nodename} | {error, Reason}</name> <fsummary>Add a remote node to the list of traced nodes</fsummary> <type> <v>Nodename = atom()</v> @@ -767,7 +767,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>cn(Nodename) -> ok</name> + <name since="">cn(Nodename) -> ok</name> <fsummary>Clear a node from the list of traced nodes.</fsummary> <type> <v>Nodename = atom()</v> @@ -782,14 +782,14 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>ln() -> ok</name> + <name since="">ln() -> ok</name> <fsummary>Show the list of traced nodes on the console.</fsummary> <desc> <p>Shows the list of traced nodes on the console.</p> </desc> </func> <func> - <name>tracer() -> {ok, pid()} | {error, already_started}</name> + <name since="">tracer() -> {ok, pid()} | {error, already_started}</name> <fsummary>Start a tracer server that handles trace messages.</fsummary> <desc> <p>This function starts a server on the local node that will @@ -805,7 +805,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>tracer(Type, Data) -> {ok, pid()} | {error, Error}</name> + <name since="">tracer(Type, Data) -> {ok, pid()} | {error, Error}</name> <fsummary>Start a tracer server with additional parameters</fsummary> <type> <v>Type = port | process | module</v> @@ -859,7 +859,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name> + <name since="">tracer(Nodename, Type, Data) -> {ok, Nodename} | {error, Reason}</name> <fsummary>Start a tracer server on given node with additional parameters</fsummary> <type> <v>Nodename = atom()</v> @@ -881,7 +881,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>trace_port(Type, Parameters) -> fun()</name> + <name since="">trace_port(Type, Parameters) -> fun()</name> <fsummary>Create and returns a trace port generating<em>fun</em></fsummary> <type> <v>Type = ip | file</v> @@ -958,28 +958,28 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>flush_trace_port()</name> + <name since="">flush_trace_port()</name> <fsummary>Equivalent to flush_trace_port(node()).</fsummary> <desc> <p>Equivalent to <c>flush_trace_port(node())</c>.</p> </desc> </func> <func> - <name>flush_trace_port(Nodename) -> ok | {error, Reason}</name> + <name since="">flush_trace_port(Nodename) -> ok | {error, Reason}</name> <fsummary>Flush internal data buffers in a trace driver on the given node.</fsummary> <desc> <p>Equivalent to <c>trace_port_control(Nodename,flush)</c>.</p> </desc> </func> <func> - <name>trace_port_control(Operation)</name> + <name since="">trace_port_control(Operation)</name> <fsummary>Equivalent to trace_port_control(node(),Operation).</fsummary> <desc> <p>Equivalent to <c>trace_port_control(node(),Operation)</c>.</p> </desc> </func> <func> - <name>trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name> + <name since="">trace_port_control(Nodename,Operation) -> ok | {ok, Result} | {error, Reason}</name> <fsummary>Perform a control operation on the active trace port driver on the given node.</fsummary> <type> <v>Nodename = atom()</v> @@ -1013,7 +1013,7 @@ Error: fun containing local erlang function calls ('is_atomm' called in guard)\ </desc> </func> <func> - <name>trace_client(Type, Parameters) -> pid()</name> + <name since="">trace_client(Type, Parameters) -> pid()</name> <fsummary>Start a trace client that reads messages created by a trace port driver</fsummary> <type> <v>Type = ip | file | follow_file</v> @@ -1080,7 +1080,7 @@ hello</pre> </desc> </func> <func> - <name>trace_client(Type, Parameters, HandlerSpec) -> pid()</name> + <name since="">trace_client(Type, Parameters, HandlerSpec) -> pid()</name> <fsummary>Start a trace client that reads messages created by a trace port driver, with a user defined handler</fsummary> <type> <v>Type = ip | file | follow_file</v> @@ -1110,7 +1110,7 @@ hello</pre> </desc> </func> <func> - <name>stop_trace_client(Pid) -> ok</name> + <name since="">stop_trace_client(Pid) -> ok</name> <fsummary>Stop a trace client gracefully.</fsummary> <type> <v>Pid = pid()</v> @@ -1123,14 +1123,14 @@ hello</pre> </desc> </func> <func> - <name>get_tracer()</name> + <name since="">get_tracer()</name> <fsummary>Equivalent to get_tracer(node()).</fsummary> <desc> <p>Equivalent to <c>get_tracer(node())</c>.</p> </desc> </func> <func> - <name>get_tracer(Nodename) -> {ok, Tracer}</name> + <name since="">get_tracer(Nodename) -> {ok, Tracer}</name> <fsummary>Return the process or port to which all trace messages are sent.</fsummary> <type> <v>Nodename = atom()</v> @@ -1142,7 +1142,7 @@ hello</pre> </desc> </func> <func> - <name>stop() -> ok</name> + <name since="">stop() -> ok</name> <fsummary>Stop the <c>dbg</c>server and the tracing of all processes.</fsummary> <desc> <p>Stops the <c>dbg</c> server and clears all trace flags for @@ -1153,7 +1153,7 @@ hello</pre> </desc> </func> <func> - <name>stop_clear() -> ok</name> + <name since="">stop_clear() -> ok</name> <fsummary>Stop the <c>dbg</c>server and the tracing of all processes, and clears trace patterns.</fsummary> <desc> <p>Same as stop/0, but also clears all trace patterns on global functions calls.</p> diff --git a/lib/runtime_tools/doc/src/dyntrace.xml b/lib/runtime_tools/doc/src/dyntrace.xml index 0cdcecab68..4935dfcd71 100644 --- a/lib/runtime_tools/doc/src/dyntrace.xml +++ b/lib/runtime_tools/doc/src/dyntrace.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>dyntrace.xml</file> </header> - <module>dyntrace</module> + <module since="OTP R15B01">dyntrace</module> <modulesummary>Interface to dynamic tracing</modulesummary> <description> <p>This module implements interfaces to dynamic tracing, should such be compiled into the virtual machine. For a standard and/or commercial build, no dynamic tracing is available, in which case none of the functions in this module is usable or give any effect.</p> @@ -47,7 +47,7 @@ </description> <funcs> <func> - <name>available() -> boolean()</name> + <name since="OTP R15B01">available() -> boolean()</name> <fsummary>Check if dynamic tracing is available</fsummary> <desc> <p>This function uses the NIF library to determine if dynamic @@ -59,42 +59,42 @@ </desc> </func> <func> - <name>p() -> true | false | error | badarg</name> + <name since="OTP R15B01">p() -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message only containing the user tag and zeroes/empty strings in all other fields.</p> </desc> </func> <func> - <name>p(integer() | string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer() | string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer or string parameter in the first integer/string field.</p> </desc> </func> <func> - <name>p(integer() | string(), integer() | string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer() | string(), integer() | string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters. I.e. <c>p(1,"Hello")</c> is ok, as is <c>p(1,1)</c> and <c>p("Hello","Again")</c>, but not <c>p("Hello",1)</c>.</p> </desc> </func> <func> - <name>p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p> </desc> </func> <func> - <name>p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer() | string(), integer() | string(), integer() | string(), integer() | string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p> </desc> </func> <func> - <name>p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer(), integer() | string(), integer() | string(), integer() | string(), string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p> @@ -102,7 +102,7 @@ </desc> </func> <func> - <name>p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer(), integer(), integer() | string(), integer() | string(), string(), string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p> @@ -110,7 +110,7 @@ </desc> </func> <func> - <name>p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer(), integer(), integer(), integer() | string(), string(), string(), string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing the user tag and the integer() or string() parameters as the first fields of respective type. integer() parameters should be put before any string() parameters, as in <seealso marker="#p/2">p/2</seealso>.</p> @@ -118,14 +118,14 @@ </desc> </func> <func> - <name>p(integer(), integer(), integer(), integer(), string(), string(), string(), string()) -> true | false | error | badarg</name> + <name since="OTP R15B01">p(integer(), integer(), integer(), integer(), string(), string(), string(), string()) -> true | false | error | badarg</name> <fsummary>Trigger the user trace probe.</fsummary> <desc> <p>Calling this function will trigger the "user" trace probe user_trace_i4s4 in the dyntrace NIF module, sending a trace message containing all the integer()'s and string()'s provided, as well as any user tag set in the current process.</p> </desc> </func> <func> - <name>get_tag() -> binary() | undefined</name> + <name since="OTP R15B01">get_tag() -> binary() | undefined</name> <fsummary>Get the user tag set in the process.</fsummary> <desc> <p>This function returns the user tag set in the current @@ -134,7 +134,7 @@ </desc> </func> <func> - <name>get_tag() -> binary() | undefined</name> + <name since="OTP R15B01">get_tag() -> binary() | undefined</name> <fsummary>Get the user tag set in the process or sent to the process.</fsummary> <desc> <p>This function returns the user tag set in the current @@ -151,7 +151,7 @@ </func> <func> - <name>put_tag(Item) -> binary() | undefined </name> + <name since="OTP R15B01">put_tag(Item) -> binary() | undefined </name> <fsummary>Set the user tag of the current process.</fsummary> <type> <v>Item = iodata()</v> @@ -163,7 +163,7 @@ </desc> </func> <func> - <name>spread_tag(boolean()) -> TagData</name> + <name since="OTP R15B01">spread_tag(boolean()) -> TagData</name> <fsummary>Start or stop spreading dynamic trace user tags with the next message.</fsummary> <type> <v>TagData = opaque data that can be used as parameter to <seealso marker="#restore_tag/1">restore_tag/1</seealso></v> @@ -185,7 +185,7 @@ f() -> </desc> </func> <func> - <name>restore_tag(TagData) -> true</name> + <name since="OTP R15B01">restore_tag(TagData) -> true</name> <fsummary>Restore to a previous state of user tag spreading.</fsummary> <type> <v>TagData = opaque data returned by <seealso marker="#spread_tag/1">spread_tag/1</seealso></v> diff --git a/lib/runtime_tools/doc/src/erts_alloc_config.xml b/lib/runtime_tools/doc/src/erts_alloc_config.xml index ffc4ec5285..5bcce1b5e3 100644 --- a/lib/runtime_tools/doc/src/erts_alloc_config.xml +++ b/lib/runtime_tools/doc/src/erts_alloc_config.xml @@ -29,7 +29,7 @@ <rev>1</rev> <file>erts_alloc_config.sgml</file> </header> - <module>erts_alloc_config</module> + <module since="">erts_alloc_config</module> <modulesummary>Configuration tool for erts_alloc</modulesummary> <description> <note> @@ -136,7 +136,7 @@ </description> <funcs> <func> - <name>save_scenario() -> ok | {error, Error}</name> + <name since="">save_scenario() -> ok | {error, Error}</name> <fsummary>Saves information about current runtime scenario</fsummary> <type> <v>Error = term()</v> @@ -154,7 +154,7 @@ </desc> </func> <func> - <name>make_config() -> ok | {error, Error}</name> + <name since="">make_config() -> ok | {error, Error}</name> <fsummary>Creates an erts_alloc configuration</fsummary> <type> <v>Error = term()</v> @@ -165,7 +165,7 @@ </desc> </func> <func> - <name>make_config(FileNameOrIODev) -> ok | {error, Error}</name> + <name since="">make_config(FileNameOrIODev) -> ok | {error, Error}</name> <fsummary>Creates an erts_alloc configuration</fsummary> <type> <v>FileNameOrIODev = string() | io_device()</v> @@ -190,7 +190,7 @@ </desc> </func> <func> - <name>stop() -> ok | {error, Error}</name> + <name since="">stop() -> ok | {error, Error}</name> <fsummary></fsummary> <type> <v>Error = term()</v> diff --git a/lib/runtime_tools/doc/src/msacc.xml b/lib/runtime_tools/doc/src/msacc.xml index 129da3d230..ae089de8d0 100644 --- a/lib/runtime_tools/doc/src/msacc.xml +++ b/lib/runtime_tools/doc/src/msacc.xml @@ -31,7 +31,7 @@ <rev>A</rev> <file>msacc.xml</file> </header> - <module>msacc</module> + <module since="OTP 19.0">msacc</module> <modulesummary>Convenience functions for microstate accounting</modulesummary> <description> <p>This module implements some convenience functions for analyzing @@ -146,7 +146,7 @@ ok </datatypes> <funcs> <func> - <name name="available" arity="0"/> + <name name="available" arity="0" since="OTP 19.0"/> <fsummary>Check if microstate accounting is available</fsummary> <desc> <p>This function checks whether microstate accounting @@ -154,7 +154,7 @@ ok </desc> </func> <func> - <name name="start" arity="0"/> + <name name="start" arity="0" since="OTP 19.0"/> <fsummary>Start microstate accounting.</fsummary> <desc> <p>Start microstate accounting. Returns whether it was @@ -162,7 +162,7 @@ ok </desc> </func> <func> - <name name="start" arity="1"/> + <name name="start" arity="1" since="OTP 19.0"/> <fsummary>Start microstate accounting for a time.</fsummary> <desc> <p>Resets all counters and then starts microstate accounting @@ -170,7 +170,7 @@ ok </desc> </func> <func> - <name name="stop" arity="0"/> + <name name="stop" arity="0" since="OTP 19.0"/> <fsummary>Stop microstate accounting.</fsummary> <desc> <p>Stop microstate accounting. @@ -178,7 +178,7 @@ ok </desc> </func> <func> - <name name="reset" arity="0"/> + <name name="reset" arity="0" since="OTP 19.0"/> <fsummary>Reset microstate accounting counters</fsummary> <desc> <p>Reset microstate accounting counters. @@ -186,7 +186,7 @@ ok </desc> </func> <func> - <name name="print" arity="0"/> + <name name="print" arity="0" since="OTP 19.0"/> <fsummary>Print microstate statistics</fsummary> <desc> <p> @@ -199,7 +199,7 @@ ok </desc> </func> <func> - <name name="print" arity="1"/> + <name name="print" arity="1" since="OTP 19.0"/> <fsummary>Print microstate statistics</fsummary> <desc> <p>Print the given microstate statistics values to stdout. @@ -211,7 +211,7 @@ ok </desc> </func> <func> - <name name="print" arity="2"/> + <name name="print" arity="2" since="OTP 19.0"/> <fsummary>Print microstate statistics</fsummary> <desc> <p>Print the given microstate statistics values to standard out. @@ -234,7 +234,7 @@ ok </desc> </func> <func> - <name name="print" arity="3"/> + <name name="print" arity="3" since="OTP 19.0"/> <fsummary>Print microstate statistics</fsummary> <desc> <p>Print the given microstate statistics values to the given file @@ -243,7 +243,7 @@ ok </desc> </func> <func> - <name name="stats" arity="0"/> + <name name="stats" arity="0" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Returns a runtime system independent version of the microstate @@ -254,7 +254,7 @@ ok </desc> </func> <func> - <name name="stats" arity="2" clause_i="1"/> + <name name="stats" arity="2" clause_i="1" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Returns the system time for the given microstate statistics values. @@ -269,7 +269,7 @@ ok </desc> </func> <func> - <name name="stats" arity="2" clause_i="2"/> + <name name="stats" arity="2" clause_i="2" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Returns fractions of real-time or run-time spent in the various @@ -277,7 +277,7 @@ ok </desc> </func> <func> - <name name="stats" arity="2" clause_i="3"/> + <name name="stats" arity="2" clause_i="3" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Returns a list of microstate statistics values where the values @@ -285,7 +285,7 @@ ok </desc> </func> <func> - <name name="to_file" arity="1"/> + <name name="to_file" arity="1" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Dumps the current microstate statistics counters to a file that can @@ -294,7 +294,7 @@ ok </desc> </func> <func> - <name name="from_file" arity="1"/> + <name name="from_file" arity="1" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Read a file dump produced by <seealso marker="#to_file/1"> diff --git a/lib/runtime_tools/doc/src/scheduler.xml b/lib/runtime_tools/doc/src/scheduler.xml index dd8bf73bae..b033430183 100644 --- a/lib/runtime_tools/doc/src/scheduler.xml +++ b/lib/runtime_tools/doc/src/scheduler.xml @@ -32,7 +32,7 @@ <rev></rev> <file>scheduler.xml</file> </header> - <module>scheduler</module> + <module since="OTP 21.0">scheduler</module> <modulesummary>Measure scheduler utilization</modulesummary> <description> <p>This module contains utility functions for easier measurement and @@ -84,7 +84,7 @@ <funcs> <func> - <name name="sample" arity="0"/> + <name name="sample" arity="0" since="OTP 21.0"/> <fsummary>Get scheduler utilization sample.</fsummary> <desc> <p>Return a scheduler utilization sample for normal and dirty-cpu @@ -93,7 +93,7 @@ </func> <func> - <name name="sample_all" arity="0"/> + <name name="sample_all" arity="0" since="OTP 21.0"/> <fsummary>Get scheduler utilization sample.</fsummary> <desc> <p>Return a scheduler utilization sample for all schedulers, @@ -102,7 +102,7 @@ </func> <func> - <name name="utilization" arity="1" clause_i="1"/> + <name name="utilization" arity="1" clause_i="1" since="OTP 21.0"/> <fsummary>Measure scheduler utilizations during a period of time.</fsummary> <desc> <p>Measure utilization for normal and dirty-cpu schedulers during @@ -111,7 +111,7 @@ </func> <func> - <name name="utilization" arity="1" clause_i="2"/> + <name name="utilization" arity="1" clause_i="2" since="OTP 21.0"/> <fsummary>Measure scheduler utilizations since sample.</fsummary> <desc> <p>Calculate scheduler utilizations for the time interval from when @@ -121,7 +121,7 @@ </func> <func> - <name name="utilization" arity="2"/> + <name name="utilization" arity="2" since="OTP 21.0"/> <fsummary>Measure scheduler utilizations between two samples.</fsummary> <desc> <p>Calculates scheduler utilizations for the time interval between diff --git a/lib/runtime_tools/doc/src/system_information.xml b/lib/runtime_tools/doc/src/system_information.xml index 53dc595e64..a356b5c6f8 100644 --- a/lib/runtime_tools/doc/src/system_information.xml +++ b/lib/runtime_tools/doc/src/system_information.xml @@ -32,14 +32,14 @@ <rev></rev> <file>system_information.xml</file> </header> - <module>system_information</module> + <module since="OTP 17.0">system_information</module> <modulesummary>System Information</modulesummary> <description> <p></p> </description> <funcs> <func> - <name name="sanity_check" arity="0"/> + <name name="sanity_check" arity="0" since="OTP 17.0"/> <fsummary>Perform a sanity check</fsummary> <desc> <p>Performs a sanity check on the system. If no issues @@ -88,7 +88,7 @@ </desc> </func> <func> - <name name="to_file" arity="1"/> + <name name="to_file" arity="1" since="OTP 17.0"/> <fsummary>Write miscellaneous system information to file</fsummary> <desc><p>Writes miscellaneous system information to file. This information will typically be requested by the Erlang/OTP team diff --git a/lib/runtime_tools/examples/dist.systemtap b/lib/runtime_tools/examples/dist.systemtap index bb20d617e1..4102a5243c 100644 --- a/lib/runtime_tools/examples/dist.systemtap +++ b/lib/runtime_tools/examples/dist.systemtap @@ -19,18 +19,18 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("dist-monitor") +probe process("beam.smp").mark("dist-monitor") { printf("monitor: pid %d, who %s, what %s, node %s, type %s, reason %s\n", pid(), @@ -38,38 +38,38 @@ probe process("beam").mark("dist-monitor") user_string($arg5)); } -probe process("beam").mark("dist-port_busy") +probe process("beam.smp").mark("dist-port_busy") { printf("dist port_busy: node %s, port %s, remote_node %s, blocked pid %s\n", user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4)); - blocked_procs[user_string($arg4)] = timestamp; + blocked_procs[user_string($arg4)] = local_clock_ns(); } -probe process("beam").mark("dist-port_busy") +probe process("beam.smp").mark("dist-port_busy") { printf("dist port_busy: node %s, port %s, remote_node %s, blocked pid %s\n", user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4)); - blocked_procs[user_string($arg4)] = timestamp; + blocked_procs[user_string($arg4)] = local_clock_ns(); } -probe process("beam").mark("dist-output") +probe process("beam.smp").mark("dist-output") { printf("dist output: node %s, port %s, remote_node %s bytes %d\n", user_string($arg1), user_string($arg2), user_string($arg3), $arg4); } -probe process("beam").mark("dist-outputv") +probe process("beam.smp").mark("dist-outputv") { printf("port outputv: node %s, port %s, remote_node %s bytes %d\n", user_string($arg1), user_string($arg2), user_string($arg3), $arg4); } -probe process("beam").mark("process-scheduled") +probe process("beam.smp").mark("process-scheduled") { pidstr = user_string($arg1); if (pidstr in blocked_procs) { printf("blocked pid %s scheduled now, waited %d microseconds\n", - pidstr, (timestamp - blocked_procs[pidstr]) / 1000); + pidstr, (local_clock_ns() - blocked_procs[pidstr]) / 1000); delete blocked_procs[pidstr]; } } diff --git a/lib/runtime_tools/examples/driver1.systemtap b/lib/runtime_tools/examples/driver1.systemtap index e1ee8ecffc..f5bc28b42d 100644 --- a/lib/runtime_tools/examples/driver1.systemtap +++ b/lib/runtime_tools/examples/driver1.systemtap @@ -19,108 +19,102 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("driver-init") +probe process("beam.smp").mark("driver__init") { printf("driver init name %s major %d minor %d flags %d\n", user_string($arg1), $arg2, $arg3, $arg4); } -probe process("beam").mark("driver-start") +probe process("beam.smp").mark("driver__start") { printf("driver start pid %s driver name %s port %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-stop") +probe process("beam.smp").mark("driver__stop") { printf("driver stop pid %s driver name %s port %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-finish") +probe process("beam.smp").mark("driver__finish") { printf("driver finish driver name %s\n", user_string($arg1)); } -probe process("beam").mark("driver-flush") +probe process("beam.smp").mark("driver__flush") { printf("driver flush pid %s port %s port name %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-output") +probe process("beam.smp").mark("driver__output") { printf("driver output pid %s port %s port name %s bytes %d\n", user_string($arg1), user_string($arg2), user_string($arg3), $arg4); } -probe process("beam").mark("driver-outputv") +probe process("beam.smp").mark("driver__outputv") { printf("driver outputv pid %s port %s port name %s bytes %d\n", user_string($arg1), user_string($arg2), user_string($arg3), $arg4); } -probe process("beam").mark("driver-control") +probe process("beam.smp").mark("driver__control") { printf("driver control pid %s port %s port name %s command %d bytes %d\n", user_string($arg1), user_string($arg2), user_string($arg3), $arg4, $arg5); } -probe process("beam").mark("driver-call") +probe process("beam.smp").mark("driver__call") { printf("driver call pid %s port %s port name %s command %d bytes %d\n", user_string($arg1), user_string($arg2), user_string($arg3), $arg4, $arg5); } -probe process("beam").mark("driver-event") -{ - printf("driver event pid %s port %s port name %s\n", - user_string($arg1), user_string($arg2), user_string($arg3)); -} - -probe process("beam").mark("driver-ready_input") +probe process("beam.smp").mark("driver__ready_input") { printf("driver ready_input pid %s port %s port name %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-ready_output") +probe process("beam.smp").mark("driver__ready_output") { printf("driver ready_output pid %s port %s port name %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-timeout") +probe process("beam.smp").mark("driver__timeout") { printf("driver timeout pid %s port %s port name %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-ready_async") +probe process("beam.smp").mark("driver__ready_async") { printf("driver ready_async pid %s port %s port name %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-process_exit") +probe process("beam.smp").mark("driver__process_exit") { printf("driver process_exit pid %s port %s port name %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("driver-stop_select") +probe process("beam.smp").mark("driver__stop_select") { printf("driver stop_select driver name %s\n", user_string($arg1)); } diff --git a/lib/runtime_tools/examples/function-calls.systemtap b/lib/runtime_tools/examples/function-calls.systemtap index 9c44b2d014..6bb173b3ec 100644 --- a/lib/runtime_tools/examples/function-calls.systemtap +++ b/lib/runtime_tools/examples/function-calls.systemtap @@ -18,51 +18,51 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("local-function-entry") +probe process("beam.smp").mark("local-function-entry") { printf("pid %s enter (local) %s depth %d\n", user_string($arg1), user_string($arg2), $arg3); } -probe process("beam").mark("global-function-entry") +probe process("beam.smp").mark("global-function-entry") { printf("pid %s enter (global) %s depth %d\n", user_string($arg1), user_string($arg2), $arg3); } -probe process("beam").mark("function-return") +probe process("beam.smp").mark("function-return") { printf("pid %s return %s depth %d\n", user_string($arg1), user_string($arg2), $arg3); } -probe process("beam").mark("bif-entry") +probe process("beam.smp").mark("bif-entry") { printf("pid %s BIF entry mfa %s\n", user_string($arg1), user_string($arg2)); } -probe process("beam").mark("bif-return") +probe process("beam.smp").mark("bif-return") { printf("pid %s BIF return mfa %s\n", user_string($arg1), user_string($arg2)); } -probe process("beam").mark("nif-entry") +probe process("beam.smp").mark("nif-entry") { printf("pid %s NIF entry mfa %s\n", user_string($arg1), user_string($arg2)); } -probe process("beam").mark("nif-return") +probe process("beam.smp").mark("nif-return") { printf("pid %s NIF return mfa %s\n", user_string($arg1), user_string($arg2)); } diff --git a/lib/runtime_tools/examples/garbage-collection.systemtap b/lib/runtime_tools/examples/garbage-collection.systemtap index e414eea821..14f0d6851c 100644 --- a/lib/runtime_tools/examples/garbage-collection.systemtap +++ b/lib/runtime_tools/examples/garbage-collection.systemtap @@ -18,33 +18,33 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("gc_major-start") +probe process("beam.smp").mark("gc_major-start") { printf("GC major start pid %s need %d words\n", user_string($arg1), $arg2); } -probe process("beam").mark("gc_minor-start") +probe process("beam.smp").mark("gc_minor-start") { printf("GC minor start pid %s need %d words\n", user_string($arg1), $arg2); } -probe process("beam").mark("gc_major-end") +probe process("beam.smp").mark("gc_major-end") { printf("GC major end pid %s reclaimed %d words\n", user_string($arg1), $arg2); } -probe process("beam").mark("gc_minor-start") +probe process("beam.smp").mark("gc_minor-start") { printf("GC minor end pid %s reclaimed %d words\n", user_string($arg1), $arg2); } diff --git a/lib/runtime_tools/examples/memory1.systemtap b/lib/runtime_tools/examples/memory1.systemtap index 04df4d64c4..2fdc5a796c 100644 --- a/lib/runtime_tools/examples/memory1.systemtap +++ b/lib/runtime_tools/examples/memory1.systemtap @@ -18,34 +18,34 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("copy-struct") +probe process("beam.smp").mark("copy-struct") { printf("copy_struct %d bytes\n", $arg1); } -probe process("beam").mark("copy-object") +probe process("beam.smp").mark("copy-object") { printf("copy_object pid %s %d bytes\n", user_string($arg1), $arg2); } -probe process("beam").mark("process-heap_grow") +probe process("beam.smp").mark("process-heap_grow") { printf("proc heap grow pid %s %d -> %d bytes\n", user_string($arg1), $arg2, $arg3); } -probe process("beam").mark("process-heap_shrink") +probe process("beam.smp").mark("process-heap_shrink") { printf("proc heap shrink pid %s %d -> %d bytes\n", user_string($arg1), $arg2, $arg3); diff --git a/lib/runtime_tools/examples/messages.systemtap b/lib/runtime_tools/examples/messages.systemtap index f2ef56a22b..49b7f46d69 100644 --- a/lib/runtime_tools/examples/messages.systemtap +++ b/lib/runtime_tools/examples/messages.systemtap @@ -18,15 +18,15 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ probe begin @@ -38,7 +38,7 @@ probe begin printf("\n"); } -probe process("beam").mark("message-send") +probe process("beam.smp").mark("message-send") { if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) { printf("send: %s -> %s: %d words\n", @@ -51,7 +51,7 @@ probe process("beam").mark("message-send") } } -probe process("beam").mark("message-send-remote") +probe process("beam.smp").mark("message-send-remote") { if ($arg5 == 0 && $arg6 == 0 && $arg7 == 0) { printf("send : %s -> %s %s: %d words\n", @@ -64,7 +64,7 @@ probe process("beam").mark("message-send-remote") } } -probe process("beam").mark("message-queued") +probe process("beam.smp").mark("message-queued") { if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) { printf("queued: %s: %d words, queue len %d\n", user_string($arg1), $arg2, $arg3); @@ -75,7 +75,7 @@ probe process("beam").mark("message-queued") } } -probe process("beam").mark("message-receive") +probe process("beam.smp").mark("message-receive") { if ($arg4 == 0 && $arg5 == 0 && $arg6 == 0) { printf("receive: %s: %d words, queue len %d\n", diff --git a/lib/runtime_tools/examples/port1.systemtap b/lib/runtime_tools/examples/port1.systemtap index f7ce03a65e..235581b0b1 100644 --- a/lib/runtime_tools/examples/port1.systemtap +++ b/lib/runtime_tools/examples/port1.systemtap @@ -18,15 +18,15 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ probe begin @@ -96,19 +96,19 @@ probe begin driver_map["udp_inet", 62] = "BINDX"; } -probe process("beam").mark("port-open") +probe process("beam.smp").mark("port-open") { printf("port open pid %s port name %s port %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("port-command") +probe process("beam.smp").mark("port-command") { printf("port command pid %s port %s port name %s command type %s\n", user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4)); } -probe process("beam").mark("port-control") +probe process("beam.smp").mark("port-control") { cmd = driver_map[user_string($arg3), $arg4]; cmd_str = (cmd == "") ? "unknown" : cmd; @@ -118,36 +118,36 @@ probe process("beam").mark("port-control") /* port-exit is fired as a result of port_close() or exit signal */ -probe process("beam").mark("port-exit") +probe process("beam.smp").mark("port-exit") { printf("port exit pid %s port %s port name %s reason %s\n", user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4)); } -probe process("beam").mark("port-connect") +probe process("beam.smp").mark("port-connect") { printf("port connect pid %s port %s port name %s new pid %s\n", user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4)); } -probe process("beam").mark("port-busy") +probe process("beam.smp").mark("port-busy") { printf("port busy %s\n", user_string($arg1)); } -probe process("beam").mark("port-not_busy") +probe process("beam.smp").mark("port-not_busy") { printf("port not busy %s\n", user_string($arg1)); } -probe process("beam").mark("aio_pool-add") +probe process("beam.smp").mark("aio_pool-add") { printf("async I/O pool add thread %d queue len %d\n", $arg1, $arg2); } -probe process("beam").mark("aio_pool-get") +probe process("beam.smp").mark("aio_pool-get") { printf("async I/O pool get thread %d queue len %d\n", $arg1, $arg2); } -global driver_map;
\ No newline at end of file +global driver_map; diff --git a/lib/runtime_tools/examples/process-scheduling.systemtap b/lib/runtime_tools/examples/process-scheduling.systemtap index b0b74257b3..231c589f64 100644 --- a/lib/runtime_tools/examples/process-scheduling.systemtap +++ b/lib/runtime_tools/examples/process-scheduling.systemtap @@ -18,28 +18,28 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("process-scheduled") +probe process("beam.smp").mark("process-scheduled") { printf(" Schedule pid %s mfa %s\n", user_string($arg1), user_string($arg2)); } -probe process("beam").mark("process-unscheduled") +probe process("beam.smp").mark("process-unscheduled") { printf("Unschedule pid %s\n", user_string($arg1)); } -probe process("beam").mark("process-hibernate") +probe process("beam.smp").mark("process-hibernate") { printf(" Hibernate pid %s resume mfa %s\n", user_string($arg1), user_string($arg2)); diff --git a/lib/runtime_tools/examples/spawn-exit.systemtap b/lib/runtime_tools/examples/spawn-exit.systemtap index 89bca14496..a7b4a0a3ea 100644 --- a/lib/runtime_tools/examples/spawn-exit.systemtap +++ b/lib/runtime_tools/examples/spawn-exit.systemtap @@ -18,34 +18,34 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("process-spawn") +probe process("beam.smp").mark("process-spawn") { printf("pid %s mfa %s\n", user_string($arg1), user_string($arg2)); } -probe process("beam").mark("process-exit") +probe process("beam.smp").mark("process-exit") { printf("pid %s reason %s\n", user_string($arg1), user_string($arg2)); } -probe process("beam").mark("process-exit_signal") +probe process("beam.smp").mark("process-exit_signal") { printf("sender %s -> pid %s reason %s\n", user_string($arg1), user_string($arg2), user_string($arg3)); } -probe process("beam").mark("process-exit_signal-remote") +probe process("beam.smp").mark("process-exit_signal-remote") { printf("sender %s -> node %s pid %s reason %s\n", user_string($arg1), user_string($arg2), user_string($arg3), user_string($arg4)); diff --git a/lib/runtime_tools/examples/user-probe-n.systemtap b/lib/runtime_tools/examples/user-probe-n.systemtap index 25f7503283..8a0a89c931 100644 --- a/lib/runtime_tools/examples/user-probe-n.systemtap +++ b/lib/runtime_tools/examples/user-probe-n.systemtap @@ -18,18 +18,19 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("user_trace-n0") + +probe process("beam.smp").mark("user_trace-n0") { printf("probe n0: %s %s %d %d %d %d '%s' '%s' '%s' '%s'\n", user_string($arg1), @@ -41,7 +42,7 @@ probe process("beam").mark("user_trace-n0") $arg9 == NULL ? "" : user_string($arg9)); } -probe process("beam").mark("user_trace-n1") +probe process("beam.smp").mark("user_trace-n1") { printf("probe n1: %s %s %d %d %d %d '%s' '%s' '%s' '%s'\n", user_string($arg1), diff --git a/lib/runtime_tools/examples/user-probe.systemtap b/lib/runtime_tools/examples/user-probe.systemtap index 1777476e54..ce9dde30f8 100644 --- a/lib/runtime_tools/examples/user-probe.systemtap +++ b/lib/runtime_tools/examples/user-probe.systemtap @@ -18,23 +18,23 @@ * %CopyrightEnd% */ /* - * Note: This file assumes that you're using the non-SMP-enabled Erlang - * virtual machine, "beam". The SMP-enabled VM is called "beam.smp". + * Note: This file assumes that you're using the SMP-enabled Erlang + * virtual machine, "beam.smp". * Note that other variations of the virtual machine also have * different names, e.g. the debug build of the SMP-enabled VM * is "beam.debug.smp". * * To use a different virtual machine, replace each instance of - * "beam" with "beam.smp" or the VM name appropriate to your - * environment. + * "beam.smp" with "beam.debug.smp" or the VM name appropriate + * to your environment. */ -probe process("beam").mark("user_trace-s1") +probe process("beam.smp").mark("user_trace-s1") { printf("%s\n", user_string($arg1)); } -probe process("beam").mark("user_trace-i4s4") +probe process("beam.smp").mark("user_trace-i4s4") { printf("%s %s %d %d %d %d '%s' '%s' '%s' '%s'\n", user_string($arg1), diff --git a/lib/sasl/doc/src/alarm_handler.xml b/lib/sasl/doc/src/alarm_handler.xml index 4160757164..6e74f833cd 100644 --- a/lib/sasl/doc/src/alarm_handler.xml +++ b/lib/sasl/doc/src/alarm_handler.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>alarm_handler.sgml.t1</file> </header> - <module>alarm_handler</module> + <module since="">alarm_handler</module> <modulesummary>An Alarm Handling Process</modulesummary> <description> <p>The alarm handler process is a @@ -81,7 +81,7 @@ <funcs> <func> - <name>clear_alarm(AlarmId) -> void()</name> + <name since="">clear_alarm(AlarmId) -> void()</name> <fsummary>Clears the specified alarms.</fsummary> <type> <v>AlarmId = term()</v> @@ -94,7 +94,7 @@ </func> <func> - <name>get_alarms() -> [alarm()]</name> + <name since="">get_alarms() -> [alarm()]</name> <fsummary>Gets all active alarms.</fsummary> <desc> <p>Returns a list of all active alarms. This function can only @@ -103,7 +103,7 @@ </func> <func> - <name>set_alarm(alarm())</name> + <name since="">set_alarm(alarm())</name> <fsummary>Sets an alarm with an id.</fsummary> <type> <v>alarm() = {AlarmId, AlarmDescription}</v> diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml index fce032136d..982c874117 100644 --- a/lib/sasl/doc/src/notes.xml +++ b/lib/sasl/doc/src/notes.xml @@ -31,6 +31,23 @@ </header> <p>This document describes the changes made to the SASL application.</p> +<section><title>SASL 3.3</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + New <c>counters</c> and <c>atomics</c> modules supplies + access to highly efficient operations on mutable fixed + word sized variables.</p> + <p> + Own Id: OTP-13468</p> + </item> + </list> + </section> + +</section> + <section><title>SASL 3.2.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/sasl/doc/src/rb.xml b/lib/sasl/doc/src/rb.xml index d5df4fd345..0ed7e91c11 100644 --- a/lib/sasl/doc/src/rb.xml +++ b/lib/sasl/doc/src/rb.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>rb.sgml</file> </header> - <module>rb</module> + <module since="">rb</module> <modulesummary>The Report Browser Tool</modulesummary> <description> <p>The Report Browser (RB) tool is used to browse and @@ -43,8 +43,8 @@ <funcs> <func> - <name>filter(Filters)</name> - <name>filter(Filters, Dates)</name> + <name since="OTP R13B04">filter(Filters)</name> + <name since="OTP R13B04">filter(Filters, Dates)</name> <fsummary>Filters reports and displays them on the screen.</fsummary> <type> <v>Filters = [filter()]</v> @@ -86,7 +86,7 @@ </func> <func> - <name>grep(RegExp)</name> + <name since="">grep(RegExp)</name> <fsummary>Searches the reports for a regular expression.</fsummary> <type> <v>RegExp = string() | {string(), Options} | re:mp() | {re:mp(), Options}</v> @@ -109,8 +109,8 @@ </func> <func> - <name>h()</name> - <name>help()</name> + <name since="">h()</name> + <name since="">help()</name> <fsummary>Displays help information.</fsummary> <desc> <p>Displays online help information.</p> @@ -118,8 +118,8 @@ </func> <func> - <name>list()</name> - <name>list(Type)</name> + <name since="">list()</name> + <name since="">list(Type)</name> <fsummary>Lists all reports.</fsummary> <type> <v>Type = type()</v> @@ -137,8 +137,8 @@ </func> <func> - <name>log_list()</name> - <name>log_list(Type)</name> + <name since="OTP R16B02">log_list()</name> + <name since="OTP R16B02">log_list(Type)</name> <fsummary>Logs report lists.</fsummary> <type> <v>Type = type()</v> @@ -157,8 +157,8 @@ </func> <func> - <name>rescan()</name> - <name>rescan(Options)</name> + <name since="">rescan()</name> + <name since="">rescan(Options)</name> <fsummary>Rescans the report directory.</fsummary> <type> <v>Options = [opt()]</v> @@ -171,8 +171,8 @@ </func> <func> - <name>show()</name> - <name>show(Report)</name> + <name since="">show()</name> + <name since="">show(Report)</name> <fsummary>Displays reports.</fsummary> <type> <v>Report = integer() | type()</v> @@ -186,8 +186,8 @@ </func> <func> - <name>start()</name> - <name>start(Options)</name> + <name since="">start()</name> + <name since="">start(Options)</name> <fsummary>Starts the <c>rb_server</c>.</fsummary> <type> <v>Options = [opt()]</v> @@ -256,7 +256,7 @@ </func> <func> - <name>start_log(FileName)</name> + <name since="">start_log(FileName)</name> <fsummary>Redirects all output to <c>FileName</c>.</fsummary> <type> <v>FileName = string() | atom() | pid()</v> @@ -268,7 +268,7 @@ </func> <func> - <name>stop()</name> + <name since="">stop()</name> <fsummary>Stops the <c>rb_server</c>.</fsummary> <desc> <p>Stops <c>rb_server</c>.</p> @@ -276,7 +276,7 @@ </func> <func> - <name>stop_log()</name> + <name since="">stop_log()</name> <fsummary>Stops logging to file.</fsummary> <desc> <p>Closes the log file. The output from the RB tool is diff --git a/lib/sasl/doc/src/release_handler.xml b/lib/sasl/doc/src/release_handler.xml index 9ba276aeac..f8ee0306d8 100644 --- a/lib/sasl/doc/src/release_handler.xml +++ b/lib/sasl/doc/src/release_handler.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>release_handler</module> + <module since="">release_handler</module> <modulesummary>Unpacking and Installation of Release Packages</modulesummary> <description> <p>The <em>release handler</em> process belongs to the SASL @@ -168,8 +168,8 @@ <funcs> <func> - <name>check_install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name> - <name>check_install_release(Vsn,Opts) -> {ok, OtherVsn, Descr} | {error, Reason}</name> + <name since="">check_install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name> + <name since="OTP R14B04">check_install_release(Vsn,Opts) -> {ok, OtherVsn, Descr} | {error, Reason}</name> <fsummary>Checks installation of a release in the system.</fsummary> <type> <v>Vsn = OtherVsn = string()</v> @@ -202,7 +202,7 @@ </func> <func> - <name>create_RELEASES(Root, RelDir, RelFile, AppDirs) -> ok | {error, Reason}</name> + <name since="">create_RELEASES(Root, RelDir, RelFile, AppDirs) -> ok | {error, Reason}</name> <fsummary>Creates an initial <c>RELEASES</c> file.</fsummary> <type> <v>Root = RelDir = RelFile = string()</v> @@ -233,7 +233,7 @@ </func> <func> - <name>install_file(Vsn, File) -> ok | {error, Reason}</name> + <name since="">install_file(Vsn, File) -> ok | {error, Reason}</name> <fsummary>Installs a release file in the release structure.</fsummary> <type> <v>Vsn = File = string()</v> @@ -252,8 +252,8 @@ </func> <func> - <name>install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name> - <name>install_release(Vsn, [Opt]) -> {ok, OtherVsn, Descr} | {continue_after_restart, OtherVsn, Descr} | {error, Reason}</name> + <name since="">install_release(Vsn) -> {ok, OtherVsn, Descr} | {error, Reason}</name> + <name since="">install_release(Vsn, [Opt]) -> {ok, OtherVsn, Descr} | {continue_after_restart, OtherVsn, Descr} | {error, Reason}</name> <fsummary>Installs a release in the system.</fsummary> <type> <v>Vsn = OtherVsn = string()</v> @@ -383,7 +383,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>make_permanent(Vsn) -> ok | {error, Reason}</name> + <name since="">make_permanent(Vsn) -> ok | {error, Reason}</name> <fsummary>Makes the specified release version permanent.</fsummary> <type> <v>Vsn = string()</v> @@ -396,7 +396,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>remove_release(Vsn) -> ok | {error, Reason}</name> + <name since="">remove_release(Vsn) -> ok | {error, Reason}</name> <fsummary>Removes a release from the system.</fsummary> <type> <v>Vsn = string()</v> @@ -410,7 +410,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>reboot_old_release(Vsn) -> ok | {error, Reason}</name> + <name since="">reboot_old_release(Vsn) -> ok | {error, Reason}</name> <fsummary>Reboots the system from an old release.</fsummary> <type> <v>Vsn = string()</v> @@ -425,7 +425,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>set_removed(Vsn) -> ok | {error, Reason}</name> + <name since="">set_removed(Vsn) -> ok | {error, Reason}</name> <fsummary>Marks a release as removed.</fsummary> <type> <v>Vsn = string()</v> @@ -440,7 +440,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>set_unpacked(RelFile, AppDirs) -> {ok, Vsn} | {error, Reason}</name> + <name since="">set_unpacked(RelFile, AppDirs) -> {ok, Vsn} | {error, Reason}</name> <fsummary>Marks a release as unpacked.</fsummary> <type> <v>RelFile = string()</v> @@ -466,7 +466,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>unpack_release(Name) -> {ok, Vsn} | {error, Reason}</name> + <name since="">unpack_release(Name) -> {ok, Vsn} | {error, Reason}</name> <fsummary>Unpacks a release package.</fsummary> <type> <v>Name = Vsn = string()</v> @@ -482,7 +482,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>which_releases() -> [{Name, Vsn, Apps, Status}]</name> + <name since="">which_releases() -> [{Name, Vsn, Apps, Status}]</name> <fsummary>Returns all known releases.</fsummary> <type> <v>Name = Vsn = string()</v> @@ -495,7 +495,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>which_releases(Status) -> [{Name, Vsn, Apps, Status}]</name> + <name since="OTP R15B">which_releases(Status) -> [{Name, Vsn, Apps, Status}]</name> <fsummary>Returns all known releases of a specific status.</fsummary> <type> <v>Name = Vsn = string()</v> @@ -537,7 +537,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). <funcs> <func> - <name>upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name> + <name since="">upgrade_app(App, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name> <fsummary>Upgrades to a new application version.</fsummary> <type> <v>App = atom()</v> @@ -586,8 +586,8 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>downgrade_app(App, Dir) -></name> - <name>downgrade_app(App, OldVsn, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name> + <name since="">downgrade_app(App, Dir) -></name> + <name since="">downgrade_app(App, OldVsn, Dir) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name> <fsummary>Downgrades to a previous application version.</fsummary> <type> <v>App = atom()</v> @@ -633,7 +633,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>upgrade_script(App, Dir) -> {ok, NewVsn, Script}</name> + <name since="">upgrade_script(App, Dir) -> {ok, NewVsn, Script}</name> <fsummary>Finds an application upgrade script.</fsummary> <type> <v>App = atom()</v> @@ -671,7 +671,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>downgrade_script(App, OldVsn, Dir) -> {ok, Script}</name> + <name since="">downgrade_script(App, OldVsn, Dir) -> {ok, Script}</name> <fsummary>Finds an application downgrade script.</fsummary> <type> <v>App = atom()</v> @@ -710,7 +710,7 @@ release_handler:set_unpacked(RelFile, [{myapp,"1.0","/home/user"},...]). </func> <func> - <name>eval_appup_script(App, ToVsn, ToDir, Script) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name> + <name since="">eval_appup_script(App, ToVsn, ToDir, Script) -> {ok, Unpurged} | restart_emulator | {error, Reason}</name> <fsummary>Evaluates an application upgrade or downgrade script.</fsummary> <type> <v>App = atom()</v> diff --git a/lib/sasl/doc/src/systools.xml b/lib/sasl/doc/src/systools.xml index 4842c732b1..6facb8ddae 100644 --- a/lib/sasl/doc/src/systools.xml +++ b/lib/sasl/doc/src/systools.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>systools</module> + <module since="">systools</module> <modulesummary>A Set of Release Handling Tools</modulesummary> <description> <p>This module contains functions to generate boot scripts @@ -40,8 +40,8 @@ <funcs> <func> - <name>make_relup(Name, UpFrom, DownTo) -> Result</name> - <name>make_relup(Name, UpFrom, DownTo, [Opt]) -> Result</name> + <name since="">make_relup(Name, UpFrom, DownTo) -> Result</name> + <name since="">make_relup(Name, UpFrom, DownTo, [Opt]) -> Result</name> <fsummary>Generates a release upgrade file <c>relup</c>.</fsummary> <type> <v>Name = string()</v> @@ -136,8 +136,8 @@ </func> <func> - <name>make_script(Name) -> Result</name> - <name>make_script(Name, [Opt]) -> Result</name> + <name since="">make_script(Name) -> Result</name> + <name since="">make_script(Name, [Opt]) -> Result</name> <fsummary>Generates a boot script <c>.script/.boot</c>.</fsummary> <type> <v>Name = string()</v> @@ -263,8 +263,8 @@ </func> <func> - <name>make_tar(Name) -> Result</name> - <name>make_tar(Name, [Opt]) -> Result</name> + <name since="">make_tar(Name) -> Result</name> + <name since="">make_tar(Name, [Opt]) -> Result</name> <fsummary>Creates a release package.</fsummary> <type> <v>Name = string()</v> @@ -369,7 +369,7 @@ myapp-1/ebin/myapp.app </func> <func> - <name>script2boot(File) -> ok | error</name> + <name since="">script2boot(File) -> ok | error</name> <fsummary>Generates a binary version of a boot script.</fsummary> <type> <v>File = string()</v> diff --git a/lib/sasl/src/sasl.app.src b/lib/sasl/src/sasl.app.src index 5d45af0b50..293af504df 100644 --- a/lib/sasl/src/sasl.app.src +++ b/lib/sasl/src/sasl.app.src @@ -43,5 +43,5 @@ {env, []}, {mod, {sasl, []}}, {runtime_dependencies, ["tools-2.6.14","stdlib-3.4","kernel-5.3", - "erts-@OTP-13468@"]}]}. + "erts-10.2"]}]}. diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src index d37c5b3d95..26127eae84 100644 --- a/lib/sasl/src/sasl.appup.src +++ b/lib/sasl/src/sasl.appup.src @@ -16,13 +16,30 @@ %% limitations under the License. %% %% %CopyrightEnd% +%% +%% We allow upgrade from, and downgrade to all previous +%% versions from the following OTP releases: +%% - OTP 20 +%% - OTP 21 +%% +%% We also allow upgrade from, and downgrade to all +%% versions that have branched off from the above +%% stated previous versions. +%% {"%VSN%", - %% Up from - max one major revision back - [{<<"3\\.0\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 - {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"3\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21.* - %% Down to - max one major revision back - [{<<"3\\.0\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.* - {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"3\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.* -}. + [{<<"^3\\.0\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.1$">>,[restart_new_emulator]}, + {<<"^3\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.1\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.2$">>,[restart_new_emulator]}, + {<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}], + [{<<"^3\\.0\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.1$">>,[restart_new_emulator]}, + {<<"^3\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.1\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.2$">>,[restart_new_emulator]}, + {<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}. diff --git a/lib/sasl/src/systools.erl b/lib/sasl/src/systools.erl index dd1a58c3c1..34eca6679f 100644 --- a/lib/sasl/src/systools.erl +++ b/lib/sasl/src/systools.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2019. 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. @@ -73,7 +73,7 @@ make_tar(RelName, Opt) -> script2boot(File) -> case systools_lib:file_term2binary(File ++ ".script", File ++ ".boot") of {error,Error} -> - io:format(systools_make:format_error(Error)), + io:format("~ts", [systools_make:format_error(Error)]), error; _ -> ok @@ -84,7 +84,7 @@ script2boot(File, Output0, _Opt) -> Output = Output0++".boot", case systools_lib:file_term2binary(Input, Output) of {error,Error} -> - io:format(systools_make:format_error(Error)), + io:format("~ts", [systools_make:format_error(Error)]), error; _ -> ok diff --git a/lib/sasl/src/systools_relup.erl b/lib/sasl/src/systools_relup.erl index e836d57670..5f1176ec69 100644 --- a/lib/sasl/src/systools_relup.erl +++ b/lib/sasl/src/systools_relup.erl @@ -587,7 +587,7 @@ default(warnings_as_errors) -> false. print_error({error, Mod, Error}) -> S = apply(Mod, format_error, [Error]), - io:format(S, []); + io:format("~ts", [S]); print_error(Other) -> io:format("Error: ~tp~n", [Other]). diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk index 94b7f9dc56..c1f80752a7 100644 --- a/lib/sasl/vsn.mk +++ b/lib/sasl/vsn.mk @@ -1 +1 @@ -SASL_VSN = 3.2.1 +SASL_VSN = 3.3 diff --git a/lib/snmp/doc/src/snmp.xml b/lib/snmp/doc/src/snmp.xml index 480ed2e825..d20f1a8d06 100644 --- a/lib/snmp/doc/src/snmp.xml +++ b/lib/snmp/doc/src/snmp.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp.xml</file> </header> - <module>snmp</module> + <module since="">snmp</module> <modulesummary>Interface functions to the SNMP toolkit</modulesummary> <description> <p>The module <c>snmp</c> contains interface functions to the @@ -56,7 +56,7 @@ <funcs> <func> - <name>config() -> ok | {error, Reason}</name> + <name since="">config() -> ok | {error, Reason}</name> <fsummary>Configure with a simple interactive tool</fsummary> <desc> <p>A simple interactive configuration tool. Simple @@ -78,8 +78,8 @@ </func> <func> - <name>start() -> ok | {error, Reason}</name> - <name>start(Type) -> ok | {error, Reason}</name> + <name since="">start() -> ok | {error, Reason}</name> + <name since="">start(Type) -> ok | {error, Reason}</name> <fsummary>Start the SNMP application</fsummary> <type> <v>Type = start_type()</v> @@ -93,8 +93,8 @@ </func> <func> - <name>start_agent() -> ok | {error, Reason}</name> - <name>start_agent(Type) -> ok | {error, Reason}</name> + <name since="">start_agent() -> ok | {error, Reason}</name> + <name since="">start_agent(Type) -> ok | {error, Reason}</name> <fsummary>Start the agent part of the SNMP application</fsummary> <type> <v>Type = start_type()</v> @@ -117,8 +117,8 @@ </func> <func> - <name>start_manager() -> ok | {error, Reason}</name> - <name>start_manager(Type) -> ok | {error, Reason}</name> + <name since="">start_manager() -> ok | {error, Reason}</name> + <name since="">start_manager(Type) -> ok | {error, Reason}</name> <fsummary>Start the manager part of the SNMP application</fsummary> <type> <v>Type = start_type()</v> @@ -141,7 +141,7 @@ </func> <func> - <name>date_and_time() -> DateAndTime</name> + <name since="">date_and_time() -> DateAndTime</name> <fsummary>Return the current date and time as an OCTET STRING</fsummary> <type> <v>DateAndTime = [int()]</v> @@ -155,7 +155,7 @@ </func> <func> - <name>date_and_time_to_universal_time_dst(DateAndTime) -> [utc()]</name> + <name since="">date_and_time_to_universal_time_dst(DateAndTime) -> [utc()]</name> <fsummary>Convert a DateAndTime value to a list of possible utc()</fsummary> <type> <v>DateAndTime = [int()]</v> @@ -171,8 +171,8 @@ </func> <func> - <name>date_and_time_to_string(DateAndTime) -> string()</name> - <name>date_and_time_to_string(DateAndTime, Validate) -> string()</name> + <name since="">date_and_time_to_string(DateAndTime) -> string()</name> + <name since="">date_and_time_to_string(DateAndTime, Validate) -> string()</name> <fsummary>Convert a DateAndTime value to a string</fsummary> <type> <v>DateAndTime = [int()]</v> @@ -194,7 +194,7 @@ </func> <func> - <name>date_and_time_to_string2(DateAndTime) -> string()</name> + <name since="">date_and_time_to_string2(DateAndTime) -> string()</name> <fsummary>Convert a DateAndTime value to a string</fsummary> <type> <v>DateAndTime = [int()]</v> @@ -210,7 +210,7 @@ </func> <func> - <name>local_time_to_date_and_time_dst(Local) -> [DateAndTime]</name> + <name since="">local_time_to_date_and_time_dst(Local) -> [DateAndTime]</name> <fsummary>Convert a Local time value to a list of possible DateAndTime(s)</fsummary> <type> <v>Local = {{Y,Mo,D},{H,M,S}}</v> @@ -226,7 +226,7 @@ </func> <func> - <name>universal_time_to_date_and_time(UTC) -> DateAndTime</name> + <name since="">universal_time_to_date_and_time(UTC) -> DateAndTime</name> <fsummary>Convert a UTC value to DateAndTime</fsummary> <type> <v>UTC = {{Y,Mo,D},{H,M,S}}</v> @@ -241,8 +241,8 @@ </func> <func> - <name>validate_date_and_time(DateAndTime) -> bool()</name> - <name>validate_date_and_time(DateAndTime, Validate) -> bool()</name> + <name since="">validate_date_and_time(DateAndTime) -> bool()</name> + <name since="">validate_date_and_time(DateAndTime, Validate) -> bool()</name> <fsummary>Check if a DateAndTime value is correct</fsummary> <type> <v>DateAndTime = term()</v> @@ -279,7 +279,7 @@ </func> <func> - <name>passwd2localized_key(Alg, Passwd, EngineID) -> Key</name> + <name since="">passwd2localized_key(Alg, Passwd, EngineID) -> Key</name> <fsummary>Generates an localized key</fsummary> <type> <v>Alg = algorithm()</v> @@ -298,7 +298,7 @@ </func> <func> - <name>octet_string_to_bits(S) -> Val</name> + <name since="">octet_string_to_bits(S) -> Val</name> <fsummary>Convert an OCTET-STRING to BITS</fsummary> <type> <v>Val = bits()</v> @@ -312,7 +312,7 @@ </func> <func> - <name>bits_to_octet_string(B) -> Val</name> + <name since="">bits_to_octet_string(B) -> Val</name> <fsummary>Convert an OCTET-STRING to BITS</fsummary> <type> <v>Val = octet_string()</v> @@ -326,7 +326,7 @@ </func> <func> - <name>read_mib(FileName) -> {ok, mib()} | {error, Reason}</name> + <name since="">read_mib(FileName) -> {ok, mib()} | {error, Reason}</name> <fsummary></fsummary> <type> <v>FileName = string()</v> @@ -341,10 +341,10 @@ </func> <func> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name> <fsummary>Convert an Audit Trail Log to text format</fsummary> <type> <v>LogDir = string()</v> @@ -412,10 +412,10 @@ </func> <func> - <name>log_to_io(LogDir, Mibs, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Block | Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop, Block) -> ok | {ok, Cnt} | {error, Reason}</name> <fsummary>Convert an Audit Trail Log to text format</fsummary> <type> <v>LogDir = string()</v> @@ -440,7 +440,7 @@ </func> <func> - <name>change_log_size(LogName, NewSize) -> ok | {error, Reason}</name> + <name since="">change_log_size(LogName, NewSize) -> ok | {error, Reason}</name> <fsummary>Change the size of the Audit Trail Log</fsummary> <type> <v>LogName = string()</v> @@ -463,8 +463,8 @@ </func> <func> - <name>print_version_info() -> void()</name> - <name>print_version_info(Prefix) -> void()</name> + <name since="">print_version_info() -> void()</name> + <name since="">print_version_info(Prefix) -> void()</name> <fsummary>Formatted print of result of the versions functions</fsummary> <type> <v>Prefix = string() | integer()</v> @@ -484,8 +484,8 @@ </func> <func> - <name>versions1() -> {ok, Info} | {error, Reason}</name> - <name>versions2() -> {ok, Info} | {error, Reason}</name> + <name since="">versions1() -> {ok, Info} | {error, Reason}</name> + <name since="">versions2() -> {ok, Info} | {error, Reason}</name> <fsummary>Retrieve various system and application info</fsummary> <type> <v>Info = [info()]</v> @@ -504,8 +504,8 @@ </func> <func> - <name>print_versions(VersionInfo) -> void()</name> - <name>print_versions(Prefix, VersionInfo) -> void()</name> + <name since="">print_versions(VersionInfo) -> void()</name> + <name since="">print_versions(Prefix, VersionInfo) -> void()</name> <fsummary>Formatted print of result of the versions functions</fsummary> <type> <v>VersionInfo = [version_info()]</v> @@ -527,7 +527,7 @@ </func> <func> - <name>enable_trace() -> void()</name> + <name since="">enable_trace() -> void()</name> <fsummary>Starts a tracer</fsummary> <!-- <type> @@ -543,7 +543,7 @@ </func> <func> - <name>disable_trace() -> void()</name> + <name since="">disable_trace() -> void()</name> <fsummary>Stop the tracer</fsummary> <!-- <type> @@ -558,7 +558,7 @@ </func> <func> - <name>set_trace(Targets) -> void()</name> + <name since="">set_trace(Targets) -> void()</name> <fsummary>Set trace target</fsummary> <type> <v>Targets = target() | targets()</v> @@ -582,7 +582,7 @@ </func> <func> - <name>reset_trace(Targets) -> void()</name> + <name since="">reset_trace(Targets) -> void()</name> <fsummary>Reset trace target</fsummary> <type> <v>Targets = module() | modules()</v> @@ -598,7 +598,7 @@ </func> <func> - <name>set_trace(Targets, Opts) -> void()</name> + <name since="">set_trace(Targets, Opts) -> void()</name> <fsummary>Set trace target</fsummary> <type> <v>Targets = target() | targets()</v> diff --git a/lib/snmp/doc/src/snmp_community_mib.xml b/lib/snmp/doc/src/snmp_community_mib.xml index 61dea05950..9800fb6c00 100644 --- a/lib/snmp/doc/src/snmp_community_mib.xml +++ b/lib/snmp/doc/src/snmp_community_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_community_mib.xml</file> </header> - <module>snmp_community_mib</module> + <module since="">snmp_community_mib</module> <modulesummary>Instrumentation Functions for SNMP-COMMUNITY-MIB</modulesummary> <description> <p>The module <c>snmp_community_mib</c> implements the instrumentation @@ -45,7 +45,7 @@ <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-COMMUNITY-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -77,7 +77,7 @@ </func> <func> - <name>reconfigure(ConfDir) -> void()</name> + <name since="">reconfigure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-COMMUNITY-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -108,8 +108,8 @@ </func> <func> - <name>add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Ret</name> - <name>add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Ret</name> + <name since="">add_community(Idx, CommName, SecName, CtxName, TransportTag) -> Ret</name> + <name since="OTP R14B03">add_community(Idx, CommName, SecName, EngineId, CtxName, TransportTag) -> Ret</name> <fsummary>Added one community</fsummary> <type> <v>Idx = string()</v> @@ -132,7 +132,7 @@ </func> <func> - <name>delete_community(Key) -> Ret</name> + <name since="">delete_community(Key) -> Ret</name> <fsummary>Delete one community</fsummary> <type> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmp_framework_mib.xml b/lib/snmp/doc/src/snmp_framework_mib.xml index 64e5df6ff5..d84327d4d5 100644 --- a/lib/snmp/doc/src/snmp_framework_mib.xml +++ b/lib/snmp/doc/src/snmp_framework_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_framework_mib.xml</file> </header> - <module>snmp_framework_mib</module> + <module since="">snmp_framework_mib</module> <modulesummary>Instrumentation Functions for SNMP-FRAMEWORK-MIB</modulesummary> <description> <p>The module <c>snmp_framework_mib</c> implements instrumentation @@ -44,7 +44,7 @@ </description> <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-FRAMEWORK-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -75,7 +75,7 @@ </desc> </func> <func> - <name>init() -> void()</name> + <name since="">init() -> void()</name> <fsummary>Initialize the SNMP-FRAMEWORK-MIB</fsummary> <desc> <p>This function is called from the supervisor at system @@ -88,7 +88,7 @@ </desc> </func> <func> - <name>add_context(Ctx) -> Ret</name> + <name since="">add_context(Ctx) -> Ret</name> <fsummary>Added one context</fsummary> <type> <v>Ctx = string()</v> @@ -103,7 +103,7 @@ </desc> </func> <func> - <name>delete_context(Key) -> Ret</name> + <name since="">delete_context(Key) -> Ret</name> <fsummary>Delete one context</fsummary> <type> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmp_generic.xml b/lib/snmp/doc/src/snmp_generic.xml index 44762dec59..6fb714907c 100644 --- a/lib/snmp/doc/src/snmp_generic.xml +++ b/lib/snmp/doc/src/snmp_generic.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_generic.xml</file> </header> - <module>snmp_generic</module> + <module since="">snmp_generic</module> <modulesummary>Generic Functions for Implementing SNMP Objects in a Database</modulesummary> <description> <marker id="description"></marker> @@ -127,8 +127,8 @@ value() = term() <funcs> <func> - <name>get_status_col(Name, Cols)</name> - <name>get_status_col(NameDb, Cols) -> {ok, StatusVal} | false</name> + <name since="">get_status_col(Name, Cols)</name> + <name since="">get_status_col(NameDb, Cols) -> {ok, StatusVal} | false</name> <fsummary>Get the value of the status column from <c>Cols</c></fsummary> <type> <v>Name = name()</v> @@ -148,7 +148,7 @@ value() = term() </func> <func> - <name>get_index_types(Name)</name> + <name since="">get_index_types(Name)</name> <fsummary>Get the index types of <c>Name</c></fsummary> <type> <v>Name = name()</v> @@ -163,7 +163,7 @@ value() = term() </func> <func> - <name>get_table_info(Name, Item) -> table_info_result()</name> + <name since="OTP R15B01">get_table_info(Name, Item) -> table_info_result()</name> <fsummary>Get table info item of MIB table <c>Name</c></fsummary> <type> <v>Name = name()</v> @@ -187,8 +187,8 @@ value() = term() </func> <func> - <name>table_func(Op1, NameDb)</name> - <name>table_func(Op2, RowIndex, Cols, NameDb) -> Ret</name> + <name since="">table_func(Op1, NameDb)</name> + <name since="">table_func(Op2, RowIndex, Cols, NameDb) -> Ret</name> <fsummary>Default instrumentation function for tables</fsummary> <type> <v>Op1 = new | delete </v> @@ -232,7 +232,7 @@ value() = term() </func> <func> - <name>table_get_elements(NameDb, RowIndex, Cols) -> Values</name> + <name since="">table_get_elements(NameDb, RowIndex, Cols) -> Values</name> <fsummary>Get elements in a table row</fsummary> <type> <v>NameDb = name_db()</v> @@ -249,7 +249,7 @@ value() = term() </func> <func> - <name>table_next(NameDb, RestOid) -> RowIndex | endOfTable</name> + <name since="">table_next(NameDb, RestOid) -> RowIndex | endOfTable</name> <fsummary>Find the next row in the table</fsummary> <type> <v>NameDb = name_db()</v> @@ -265,7 +265,7 @@ value() = term() </func> <func> - <name>table_row_exists(NameDb, RowIndex) -> bool()</name> + <name since="">table_row_exists(NameDb, RowIndex) -> bool()</name> <fsummary>Check if a row in a table exists</fsummary> <type> <v>NameDb = name_db()</v> @@ -279,7 +279,7 @@ value() = term() </func> <func> - <name>table_set_elements(NameDb, RowIndex, Cols) -> bool()</name> + <name since="">table_set_elements(NameDb, RowIndex, Cols) -> bool()</name> <fsummary>Set elements in a table row</fsummary> <type> <v>NameDb = name_db()</v> @@ -300,8 +300,8 @@ value() = term() </func> <func> - <name>variable_func(Op1, NameDb)</name> - <name>variable_func(Op2, Val, NameDb) -> Ret</name> + <name since="">variable_func(Op1, NameDb)</name> + <name since="">variable_func(Op2, Val, NameDb) -> Ret</name> <fsummary>Default instrumentation function for tables</fsummary> <type> <v>Op1 = new | delete | get</v> @@ -325,7 +325,7 @@ value() = term() </func> <func> - <name>variable_get(NameDb) -> {value, Value} | undefined</name> + <name since="">variable_get(NameDb) -> {value, Value} | undefined</name> <fsummary>Get the value of a variable</fsummary> <type> <v>NameDb = name_db()</v> @@ -339,7 +339,7 @@ value() = term() </func> <func> - <name>variable_set(NameDb, NewVal) -> true | false</name> + <name since="">variable_set(NameDb, NewVal) -> true | false</name> <fsummary>Set a value for a variable</fsummary> <type> <v>NameDb = name_db()</v> diff --git a/lib/snmp/doc/src/snmp_index.xml b/lib/snmp/doc/src/snmp_index.xml index 646e9661a3..1497f4cf67 100644 --- a/lib/snmp/doc/src/snmp_index.xml +++ b/lib/snmp/doc/src/snmp_index.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_index.xml</file> </header> - <module>snmp_index</module> + <module since="">snmp_index</module> <modulesummary>Abstract Data Type for SNMP Indexing</modulesummary> <description> <p>The module <c>snmp_index</c> implements an Abstract @@ -159,7 +159,7 @@ get_next_pid(Oid, SnmpIndex) -> </section> <funcs> <func> - <name>delete(Index) -> true</name> + <name since="">delete(Index) -> true</name> <fsummary>Delete an index table</fsummary> <type> <v>Index = NewIndex = index()</v> @@ -173,7 +173,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>delete(Index, Key) -> NewIndex</name> + <name since="">delete(Index, Key) -> NewIndex</name> <fsummary>Delete an item from the index</fsummary> <type> <v>Index = NewIndex = index()</v> @@ -185,7 +185,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>get(Index, KeyOid) -> {ok, {KeyOid, Value}} | undefined</name> + <name since="">get(Index, KeyOid) -> {ok, {KeyOid, Value}} | undefined</name> <fsummary>Get the item with <c>KeyOid</c></fsummary> <type> <v>Index = index()</v> @@ -198,7 +198,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>get_last(Index) -> {ok, {KeyOid, Value}} | undefined</name> + <name since="">get_last(Index) -> {ok, {KeyOid, Value}} | undefined</name> <fsummary>Get the last item in the index structure</fsummary> <type> <v>Index = index()</v> @@ -210,7 +210,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>get_next(Index, KeyOid) -> {ok, {NextKeyOid, Value}} | undefined</name> + <name since="">get_next(Index, KeyOid) -> {ok, {NextKeyOid, Value}} | undefined</name> <fsummary>Get the next item</fsummary> <type> <v>Index = index()</v> @@ -224,7 +224,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>insert(Index, Key, Value) -> NewIndex</name> + <name since="">insert(Index, Key, Value) -> NewIndex</name> <fsummary>Insert an item into the index</fsummary> <type> <v>Index = NewIndex = index()</v> @@ -238,7 +238,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>key_to_oid(Index, Key) -> KeyOid</name> + <name since="">key_to_oid(Index, Key) -> KeyOid</name> <fsummary>Convert a key to an OBJECT IDENTIFIER</fsummary> <type> <v>Index = index()</v> @@ -250,7 +250,7 @@ get_next_pid(Oid, SnmpIndex) -> </desc> </func> <func> - <name>new(KeyTypes) -> Index</name> + <name since="">new(KeyTypes) -> Index</name> <fsummary>Create a new snmp index structure</fsummary> <type> <v>KeyTypes = key_types()</v> diff --git a/lib/snmp/doc/src/snmp_notification_mib.xml b/lib/snmp/doc/src/snmp_notification_mib.xml index d2e288ec15..9395edf155 100644 --- a/lib/snmp/doc/src/snmp_notification_mib.xml +++ b/lib/snmp/doc/src/snmp_notification_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_notification_mib.xml</file> </header> - <module>snmp_notification_mib</module> + <module since="">snmp_notification_mib</module> <modulesummary>Instrumentation Functions for SNMP-NOTIFICATION-MIB</modulesummary> <description> <p>The module <c>snmp_notification_mib</c> implements the @@ -43,7 +43,7 @@ </description> <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-NOTIFICATION-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -70,7 +70,7 @@ </desc> </func> <func> - <name>reconfigure(ConfDir) -> void()</name> + <name since="">reconfigure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-NOTIFICATION-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -98,7 +98,7 @@ </desc> </func> <func> - <name>add_notify(Name, Tag, Type) -> Ret</name> + <name since="">add_notify(Name, Tag, Type) -> Ret</name> <fsummary>Added one notify definition</fsummary> <type> <v>Name = string()</v> @@ -115,7 +115,7 @@ </desc> </func> <func> - <name>delete_notify(Key) -> Ret</name> + <name since="">delete_notify(Key) -> Ret</name> <fsummary>Delete one notify definition</fsummary> <type> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmp_pdus.xml b/lib/snmp/doc/src/snmp_pdus.xml index 1d086e6f48..f403b6edf4 100644 --- a/lib/snmp/doc/src/snmp_pdus.xml +++ b/lib/snmp/doc/src/snmp_pdus.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_pdus.xml</file> </header> - <module>snmp_pdus</module> + <module since="">snmp_pdus</module> <modulesummary>Encode and Decode Functions for SNMP PDUs</modulesummary> <description> <p>RFC1157, RFC1905 and/or RFC2272 should be studied carefully @@ -55,7 +55,7 @@ </description> <funcs> <func> - <name>dec_message([byte()]) -> Message</name> + <name since="">dec_message([byte()]) -> Message</name> <fsummary>Decode an SNMP Message</fsummary> <type> <v>Message = #message</v> @@ -71,7 +71,7 @@ </desc> </func> <func> - <name>dec_message_only([byte()]) -> Message</name> + <name since="">dec_message_only([byte()]) -> Message</name> <fsummary>Decode an SNMP Message, but not the data part</fsummary> <type> <v>Message = #message</v> @@ -84,7 +84,7 @@ </desc> </func> <func> - <name>dec_pdu([byte()]) -> Pdu</name> + <name since="">dec_pdu([byte()]) -> Pdu</name> <fsummary>Decode an SNMP Pdu</fsummary> <type> <v>Pdu = #pdu</v> @@ -94,7 +94,7 @@ </desc> </func> <func> - <name>dec_scoped_pdu([byte()]) -> ScopedPdu</name> + <name since="">dec_scoped_pdu([byte()]) -> ScopedPdu</name> <fsummary>Decode an SNMP ScopedPdu</fsummary> <type> <v>ScopedPdu = #scoped_pdu</v> @@ -104,7 +104,7 @@ </desc> </func> <func> - <name>dec_scoped_pdu_data([byte()]) -> ScopedPduData</name> + <name since="">dec_scoped_pdu_data([byte()]) -> ScopedPduData</name> <fsummary>Decode an SNMP ScopedPduData</fsummary> <type> <v>ScopedPduData = #scoped_pdu | EncryptedPDU</v> @@ -116,7 +116,7 @@ </desc> </func> <func> - <name>dec_usm_security_parameters([byte()]) -> UsmSecParams</name> + <name since="">dec_usm_security_parameters([byte()]) -> UsmSecParams</name> <fsummary>Decode SNMP UsmSecurityParameters</fsummary> <type> <v>UsmSecParams = #usmSecurityParameters</v> @@ -126,7 +126,7 @@ </desc> </func> <func> - <name>enc_encrypted_scoped_pdu(EncryptedScopedPdu) -> [byte()]</name> + <name since="">enc_encrypted_scoped_pdu(EncryptedScopedPdu) -> [byte()]</name> <fsummary>Encode an encrypted SNMP scopedPDU</fsummary> <type> <v>EncryptedScopedPdu = [byte()]</v> @@ -142,7 +142,7 @@ </desc> </func> <func> - <name>enc_message(Message) -> [byte()]</name> + <name since="">enc_message(Message) -> [byte()]</name> <fsummary>Encode an SNMP Message</fsummary> <type> <v>Message = #message</v> @@ -152,7 +152,7 @@ </desc> </func> <func> - <name>enc_message_only(Message) -> [byte()]</name> + <name since="">enc_message_only(Message) -> [byte()]</name> <fsummary>Encode an SNMP Message, but not the data part</fsummary> <type> <v>Message = #message</v> @@ -166,7 +166,7 @@ </desc> </func> <func> - <name>enc_pdu(Pd) -> [byte()]</name> + <name since="">enc_pdu(Pd) -> [byte()]</name> <fsummary>Encode an SNMP Pdu</fsummary> <type> <v>Pdu = #pdu</v> @@ -176,7 +176,7 @@ </desc> </func> <func> - <name>enc_scoped_pdu(ScopedPdu) -> [byte()]</name> + <name since="">enc_scoped_pdu(ScopedPdu) -> [byte()]</name> <fsummary>Encode an SNMP scopedPDU</fsummary> <type> <v>ScopedPdu = #scoped_pdu</v> @@ -190,7 +190,7 @@ </desc> </func> <func> - <name>enc_usm_security_parameters(UsmSecParams) -> [byte()]</name> + <name since="">enc_usm_security_parameters(UsmSecParams) -> [byte()]</name> <fsummary>Encode SNMP UsmSecurityParameters</fsummary> <type> <v>UsmSecParams = #usmSecurityParameters</v> diff --git a/lib/snmp/doc/src/snmp_standard_mib.xml b/lib/snmp/doc/src/snmp_standard_mib.xml index 35efbba483..eb4e2fd097 100644 --- a/lib/snmp/doc/src/snmp_standard_mib.xml +++ b/lib/snmp/doc/src/snmp_standard_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_standard_mib.xml</file> </header> - <module>snmp_standard_mib</module> + <module since="">snmp_standard_mib</module> <modulesummary>Instrumentation Functions for STANDARD-MIB and SNMPv2-MIB</modulesummary> <description> <p>The module <c>snmp_standard_mib</c> implements the instrumentation functions for the @@ -42,7 +42,7 @@ </description> <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the STANDARD-MIB and SNMPv2-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -71,8 +71,8 @@ </desc> </func> <func> - <name>inc(Name) -> void()</name> - <name>inc(Name, N) -> void()</name> + <name since="">inc(Name) -> void()</name> + <name since="">inc(Name, N) -> void()</name> <fsummary>Increment a variable in the MIB</fsummary> <type> <v>Name = atom()</v> @@ -84,7 +84,7 @@ </desc> </func> <func> - <name>reconfigure(ConfDir) -> void()</name> + <name since="">reconfigure(ConfDir) -> void()</name> <fsummary>Configure the STANDARD-MIB and SNMPv2-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -114,14 +114,14 @@ </desc> </func> <func> - <name>reset() -> void()</name> + <name since="">reset() -> void()</name> <fsummary>Reset all <c>snmp</c>counters to 0</fsummary> <desc> <p>Resets all <c>snmp</c> counters to 0.</p> </desc> </func> <func> - <name>sys_up_time() -> Time</name> + <name since="">sys_up_time() -> Time</name> <fsummary>Get the system up time</fsummary> <type> <v>Time = int()</v> diff --git a/lib/snmp/doc/src/snmp_target_mib.xml b/lib/snmp/doc/src/snmp_target_mib.xml index c3bcd3b4e3..c46edb810d 100644 --- a/lib/snmp/doc/src/snmp_target_mib.xml +++ b/lib/snmp/doc/src/snmp_target_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_target_mib.xml</file> </header> - <module>snmp_target_mib</module> + <module since="">snmp_target_mib</module> <modulesummary>Instrumentation Functions for SNMP-TARGET-MIB</modulesummary> <description> <p>The module <c>snmp_target_mib</c> implements the instrumentation @@ -57,7 +57,7 @@ <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-TARGET-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -85,7 +85,7 @@ </func> <func> - <name>reconfigure(ConfDir) -> void()</name> + <name since="">reconfigure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-TARGET-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -113,7 +113,7 @@ </func> <func> - <name>set_target_engine_id(TargetAddrName, EngineId) -> boolean()</name> + <name since="">set_target_engine_id(TargetAddrName, EngineId) -> boolean()</name> <fsummary>Set the engine id for a targetAddr row.</fsummary> <type> <v>TargetAddrName = string()</v> @@ -130,7 +130,7 @@ </func> <func> - <name>add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name> + <name since="">add_addr(Name, Domain, Addr, Timeout, Retry, TagList, Params, EngineId, TMask, MMS) -> Ret</name> <fsummary>Add one target address definition</fsummary> <type> <v>Name = string()</v> @@ -156,7 +156,7 @@ </func> <func> - <name>delete_addr(Key) -> Ret</name> + <name since="">delete_addr(Key) -> Ret</name> <fsummary>Delete one target address definition</fsummary> <type> <v>Key = term()</v> @@ -171,7 +171,7 @@ </func> <func> - <name>add_params(Name, MPModel, SecModel, SecName, SecLevel) -> Ret</name> + <name since="">add_params(Name, MPModel, SecModel, SecName, SecLevel) -> Ret</name> <fsummary>Add one target parameter definition</fsummary> <type> <v>Name = string()</v> @@ -191,7 +191,7 @@ </desc> </func> <func> - <name>delete_params(Key) -> Ret</name> + <name since="">delete_params(Key) -> Ret</name> <fsummary>Delete one target parameter definition</fsummary> <type> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmp_user_based_sm_mib.xml b/lib/snmp/doc/src/snmp_user_based_sm_mib.xml index cc376ac118..6c2203ed22 100644 --- a/lib/snmp/doc/src/snmp_user_based_sm_mib.xml +++ b/lib/snmp/doc/src/snmp_user_based_sm_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_user_based_sm_mib.xml</file> </header> - <module>snmp_user_based_sm_mib</module> + <module since="">snmp_user_based_sm_mib</module> <modulesummary>Instrumentation Functions for SNMP-USER-BASED-SM-MIB</modulesummary> <description> <p>The module <c>snmp_user_based_sm_mib</c> implements the instrumentation @@ -43,7 +43,7 @@ </description> <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-USER-BASED-SM-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -72,7 +72,7 @@ </desc> </func> <func> - <name>reconfigure(ConfDir) -> void()</name> + <name since="">reconfigure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-USER-BASED-SM-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -103,7 +103,7 @@ </desc> </func> <func> - <name>add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> Ret</name> + <name since="">add_user(EngineID, Name, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> Ret</name> <fsummary>Add one user</fsummary> <type> <v>EngineID = string()</v> @@ -130,7 +130,7 @@ </desc> </func> <func> - <name>delete_user(Key) -> Ret</name> + <name since="">delete_user(Key) -> Ret</name> <fsummary>Delete one user</fsummary> <type> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml index fdad735e71..c5e98a3eb5 100644 --- a/lib/snmp/doc/src/snmp_view_based_acm_mib.xml +++ b/lib/snmp/doc/src/snmp_view_based_acm_mib.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmp_view_based_acm_mib.xml</file> </header> - <module>snmp_view_based_acm_mib</module> + <module since="">snmp_view_based_acm_mib</module> <modulesummary>Instrumentation Functions for SNMP-VIEW-BASED-ACM-MIB</modulesummary> <description> <p>The module <c>snmp_view_based_acm_mib</c> implements the instrumentation functions for the @@ -45,7 +45,7 @@ <funcs> <func> - <name>configure(ConfDir) -> void()</name> + <name since="">configure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-VIEW-BASED-ACM-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -71,7 +71,7 @@ </func> <func> - <name>reconfigure(ConfDir) -> void()</name> + <name since="">reconfigure(ConfDir) -> void()</name> <fsummary>Configure the SNMP-VIEW-BASED-ACM-MIB</fsummary> <type> <v>ConfDir = string()</v> @@ -104,7 +104,7 @@ </func> <func> - <name>add_sec2group(SecModel, SecName, GroupName) -> Ret</name> + <name since="">add_sec2group(SecModel, SecName, GroupName) -> Ret</name> <fsummary>Add one security to group definition</fsummary> <type> <v>SecModel = v1 | v2c | usm</v> @@ -124,7 +124,7 @@ </func> <func> - <name>delete_sec2group(Key) -> Ret</name> + <name since="">delete_sec2group(Key) -> Ret</name> <fsummary>Delete one security to group definition</fsummary> <type> <v>Key = term()</v> @@ -139,7 +139,7 @@ </func> <func> - <name>add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Ret</name> + <name since="">add_access(GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV) -> Ret</name> <fsummary>Add one access definition</fsummary> <type> <v>GroupName = string()</v> @@ -163,7 +163,7 @@ </func> <func> - <name>delete_access(Key) -> Ret</name> + <name since="">delete_access(Key) -> Ret</name> <fsummary>Delete one access definition</fsummary> <type> <v>Key = term()</v> @@ -178,7 +178,7 @@ </func> <func> - <name>add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Ret</name> + <name since="">add_view_tree_fam(ViewIndex, SubTree, Status, Mask) -> Ret</name> <fsummary>Add one view tree family definition</fsummary> <type> <v>ViewIndex = integer()</v> @@ -199,7 +199,7 @@ </func> <func> - <name>delete_view_tree_fam(Key) -> Ret</name> + <name since="">delete_view_tree_fam(Key) -> Ret</name> <fsummary>Delete one view tree family definition</fsummary> <type> <v>Key = term()</v> diff --git a/lib/snmp/doc/src/snmpa.xml b/lib/snmp/doc/src/snmpa.xml index b78f14da01..dc2f4e6d66 100644 --- a/lib/snmp/doc/src/snmpa.xml +++ b/lib/snmp/doc/src/snmpa.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa.xml</file> </header> - <module>snmpa</module> + <module since="">snmpa</module> <modulesummary>Interface Functions to the SNMP toolkit agent</modulesummary> <description> <p>The module <c>snmpa</c> contains interface functions to the @@ -77,7 +77,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} <funcs> <func> - <name>add_agent_caps(SysORID, SysORDescr) -> SysORIndex</name> + <name since="">add_agent_caps(SysORID, SysORDescr) -> SysORIndex</name> <fsummary>Add an AGENT-CAPABILITY definition to the agent</fsummary> <type> <v>SysORID = oid()</v> @@ -93,7 +93,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>del_agent_caps(SysORIndex) -> void()</name> + <name since="">del_agent_caps(SysORIndex) -> void()</name> <fsummary>Delete an AGENT-CAPABILITY definition from the agent</fsummary> <type> <v>SysORIndex = integer()</v> @@ -108,7 +108,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>get_agent_caps() -> [[SysORIndex, SysORID, SysORDescr, SysORUpTime]]</name> + <name since="">get_agent_caps() -> [[SysORIndex, SysORID, SysORDescr, SysORUpTime]]</name> <fsummary>Return all AGENT-CAPABILITY definitions in the agent</fsummary> <type> <v>SysORIndex = integer()</v> @@ -125,8 +125,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>get(Agent, Vars) -> Values | {error, Reason}</name> - <name>get(Agent, Vars, Context) -> Values | {error, Reason}</name> + <name since="">get(Agent, Vars) -> Values | {error, Reason}</name> + <name since="">get(Agent, Vars, Context) -> Values | {error, Reason}</name> <fsummary>Perform a get operation on the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -150,8 +150,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>get_next(Agent, Vars) -> Values | {error, Reason}</name> - <name>get_next(Agent, Vars, Context) -> Values | {error, Reason}</name> + <name since="">get_next(Agent, Vars) -> Values | {error, Reason}</name> + <name since="">get_next(Agent, Vars, Context) -> Values | {error, Reason}</name> <fsummary>Perform a get-next operation on the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -176,7 +176,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} <!-- <func> - <name>get_symbolic_store_db() -> Db</name> + <name since="">get_symbolic_store_db() -> Db</name> <fsummary>Retrieve the symbolic store database reference</fsummary> <type> <v>Db = term()</v> @@ -193,8 +193,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} --> <func> - <name>backup(BackupDir) -> ok | {error, Reason}</name> - <name>backup(Agent, BackupDir) -> ok | {error, Reason}</name> + <name since="">backup(BackupDir) -> ok | {error, Reason}</name> + <name since="">backup(Agent, BackupDir) -> ok | {error, Reason}</name> <fsummary>Backup agent data</fsummary> <type> <v>BackupDir = string()</v> @@ -216,8 +216,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </desc> </func> <func> - <name>info() -> [{Key, Value}]</name> - <name>info(Agent) -> [{Key, Value}]</name> + <name since="">info() -> [{Key, Value}]</name> + <name since="">info(Agent) -> [{Key, Value}]</name> <fsummary>Return information about the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -236,7 +236,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>old_info_format(NewInfo) -> OldInfo</name> + <name since="">old_info_format(NewInfo) -> OldInfo</name> <fsummary>Return information about the agent</fsummary> <type> <v>OldInfo = NewInfo = [{Key, Value}]</v> @@ -251,8 +251,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>load_mib(Mib) -> ok | {error, Reason}</name> - <name>load_mib(Agent, Mib) -> ok | {error, Reason}</name> + <name since="OTP R16B02">load_mib(Mib) -> ok | {error, Reason}</name> + <name since="OTP R16B02">load_mib(Agent, Mib) -> ok | {error, Reason}</name> <fsummary>Load single MIB into the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -273,10 +273,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>load_mibs(Mibs) -> ok | {error, Reason}</name> - <name>load_mibs(Mibs, Force) -> ok | {error, Reason}</name> - <name>load_mibs(Agent, Mibs) -> ok | {error, Reason}</name> - <name>load_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name> + <name since="">load_mibs(Mibs) -> ok | {error, Reason}</name> + <name since="">load_mibs(Mibs, Force) -> ok | {error, Reason}</name> + <name since="">load_mibs(Agent, Mibs) -> ok | {error, Reason}</name> + <name since="OTP R16B02">load_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name> <fsummary>Load MIBs into the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -305,8 +305,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>unload_mib(Mib) -> ok | {error, Reason}</name> - <name>unload_mib(Agent, Mib) -> ok | {error, Reason}</name> + <name since="OTP R16B02">unload_mib(Mib) -> ok | {error, Reason}</name> + <name since="OTP R16B02">unload_mib(Agent, Mib) -> ok | {error, Reason}</name> <fsummary>Unload single MIB from the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -321,10 +321,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>unload_mibs(Mibs) -> ok | {error, Reason}</name> - <name>unload_mibs(Mibs, Force) -> ok | {error, Reason}</name> - <name>unload_mibs(Agent, Mibs) -> ok | {error, Reason}</name> - <name>unload_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name> + <name since="">unload_mibs(Mibs) -> ok | {error, Reason}</name> + <name since="">unload_mibs(Mibs, Force) -> ok | {error, Reason}</name> + <name since="">unload_mibs(Agent, Mibs) -> ok | {error, Reason}</name> + <name since="OTP R16B02">unload_mibs(Agent, Mibs, Force) -> ok | {error, Reason}</name> <fsummary>Unload MIBs from the agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -347,8 +347,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_mibs() -> Mibs</name> - <name>which_mibs(Agent) -> Mibs</name> + <name since="">which_mibs() -> Mibs</name> + <name since="">which_mibs(Agent) -> Mibs</name> <fsummary>Get a list of all the loaded mibs</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -365,8 +365,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>whereis_mib(MibName) -> {ok, MibFile} | {error, Reason}</name> - <name>whereis_mib(Agent, MibName) -> {ok, MibFile} | {error, Reason}</name> + <name since="">whereis_mib(MibName) -> {ok, MibFile} | {error, Reason}</name> + <name since="">whereis_mib(Agent, MibName) -> {ok, MibFile} | {error, Reason}</name> <fsummary>Get the path to the mib file</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -385,10 +385,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>current_request_id() -> {value, RequestId} | false</name> - <name>current_context() -> {value, Context} | false</name> - <name>current_community() -> {value, Community} | false</name> - <name>current_address() -> {value, Address} | false</name> + <name since="">current_request_id() -> {value, RequestId} | false</name> + <name since="">current_context() -> {value, Context} | false</name> + <name since="">current_community() -> {value, Community} | false</name> + <name since="">current_address() -> {value, Address} | false</name> <fsummary>Get the request-id, context, community and address of the current request</fsummary> <type> <v>RequestId = integer()</v> @@ -409,8 +409,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>enum_to_int(Name, Enum) -> {value, Int} | false</name> - <name>enum_to_int(Db, Name, Enum) -> {value, Int} | false</name> + <name since="">enum_to_int(Name, Enum) -> {value, Int} | false</name> + <name since="">enum_to_int(Db, Name, Enum) -> {value, Int} | false</name> <fsummary>Convert an enum value to an integer</fsummary> <type> <v>Db = term()</v> @@ -435,8 +435,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>int_to_enum(Name, Int) -> {value, Enum} | false</name> - <name>int_to_enum(Db, Name, Int) -> {value, Enum} | false</name> + <name since="">int_to_enum(Name, Int) -> {value, Enum} | false</name> + <name since="">int_to_enum(Db, Name, Int) -> {value, Enum} | false</name> <fsummary>Convert an integer to an enum value</fsummary> <type> <v>Db = term()</v> @@ -461,8 +461,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>name_to_oid(Name) -> {value, oid()} | false</name> - <name>name_to_oid(Db, Name) -> {value, oid()} | false</name> + <name since="">name_to_oid(Name) -> {value, oid()} | false</name> + <name since="">name_to_oid(Db, Name) -> {value, oid()} | false</name> <fsummary>Convert a symbolic name to an OID</fsummary> <type> <v>Db = term()</v> @@ -482,8 +482,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>oid_to_name(OID) -> {value, Name} | false</name> - <name>oid_to_name(Db, OID) -> {value, Name} | false</name> + <name since="">oid_to_name(OID) -> {value, Name} | false</name> + <name since="">oid_to_name(Db, OID) -> {value, Name} | false</name> <fsummary>Convert an OID to a symbolic name</fsummary> <type> <v>Db = term()</v> @@ -503,7 +503,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_aliasnames() -> Result</name> + <name since="">which_aliasnames() -> Result</name> <fsummary>Get all alias-names known to the agent</fsummary> <type> <v>Result = [atom()]</v> @@ -515,7 +515,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_tables() -> Result</name> + <name since="">which_tables() -> Result</name> <fsummary>Get all tables known to the agent</fsummary> <type> <v>Result = [atom()]</v> @@ -528,7 +528,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_variables() -> Result</name> + <name since="">which_variables() -> Result</name> <fsummary>Get all variables known to the agent</fsummary> <type> <v>Result = [atom()]</v> @@ -541,7 +541,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_notifications() -> Result</name> + <name since="">which_notifications() -> Result</name> <fsummary>Get all notifications known to the agent</fsummary> <type> <v>Result = [{Name, MibName, Info}]</v> @@ -557,15 +557,15 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>log_to_txt(LogDir)</name> - <name>log_to_txt(LogDir, Block | Mibs)</name> - <name>log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_txt(LogDir)</name> + <name since="">log_to_txt(LogDir, Block | Mibs)</name> + <name since="">log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> <fsummary>Convert an Audit Trail Log to text format</fsummary> <type> <v>LogDir = string()</v> @@ -600,14 +600,14 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> <fsummary>Convert an Audit Trail Log to text format</fsummary> <type> <v>LogDir = string()</v> @@ -641,7 +641,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>change_log_size(NewSize) -> ok | {error, Reason}</name> + <name since="">change_log_size(NewSize) -> ok | {error, Reason}</name> <fsummary>Change the size of the Audit Trail Log</fsummary> <type> <v>NewSize = {MaxBytes, MaxFiles}</v> @@ -662,8 +662,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name> - <name>set_log_type(Agent, NewType) -> {ok, OldType} | {error, Reason}</name> + <name since="">set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name> + <name since="">set_log_type(Agent, NewType) -> {ok, OldType} | {error, Reason}</name> <fsummary>Change the type of the Audit Trail Log</fsummary> <type> <v>NewType = OldType = atl_type()</v> @@ -684,8 +684,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>mib_of(Oid) -> {ok, MibName} | {error, Reason}</name> - <name>mib_of(Agent, Oid) -> {ok, MibName} | {error, Reason}</name> + <name since="">mib_of(Oid) -> {ok, MibName} | {error, Reason}</name> + <name since="">mib_of(Agent, Oid) -> {ok, MibName} | {error, Reason}</name> <fsummary>Which mib an Oid belongs to</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -704,8 +704,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>me_of(Oid) -> {ok, Me} | {error, Reason}</name> - <name>me_of(Agent, Oid) -> {ok, Me} | {error, Reason}</name> + <name since="">me_of(Oid) -> {ok, Me} | {error, Reason}</name> + <name since="">me_of(Agent, Oid) -> {ok, Me} | {error, Reason}</name> <fsummary>Retrieve the mib-entry of an Oid</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -724,8 +724,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>invalidate_mibs_cache() -> void()</name> - <name>invalidate_mibs_cache(Agent) -> void()</name> + <name since="">invalidate_mibs_cache() -> void()</name> + <name since="">invalidate_mibs_cache(Agent) -> void()</name> <fsummary>Invalidate the mib server cache</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -739,8 +739,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>enable_mibs_cache() -> void()</name> - <name>enable_mibs_cache(Agent) -> void()</name> + <name since="">enable_mibs_cache() -> void()</name> + <name since="">enable_mibs_cache(Agent) -> void()</name> <fsummary>Enable the mib server cache</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -753,8 +753,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>disable_mibs_cache() -> void()</name> - <name>disable_mibs_cache(Agent) -> void()</name> + <name since="">disable_mibs_cache() -> void()</name> + <name since="">disable_mibs_cache(Agent) -> void()</name> <fsummary>Disable the mib server cache</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -767,8 +767,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_mibs_cache_size() -> void()</name> - <name>which_mibs_cache_size(Agent) -> void()</name> + <name since="OTP R14B">which_mibs_cache_size() -> void()</name> + <name since="OTP R14B">which_mibs_cache_size(Agent) -> void()</name> <fsummary>The size of the mib server cache</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -781,12 +781,12 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>gc_mibs_cache() -> {ok, NumElementsGCed} | {error, Reason}</name> - <name>gc_mibs_cache(Agent) -> {ok, NumElementsGCed} | {error, Reason}</name> - <name>gc_mibs_cache(Age) -> {ok, NumElementsGCed} | {error, Reason}</name> - <name>gc_mibs_cache(Agent, Age) -> {ok, NumElementsGCed} | {error, Reason}</name> - <name>gc_mibs_cache(Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name> - <name>gc_mibs_cache(Agent, Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name> + <name since="">gc_mibs_cache() -> {ok, NumElementsGCed} | {error, Reason}</name> + <name since="">gc_mibs_cache(Agent) -> {ok, NumElementsGCed} | {error, Reason}</name> + <name since="">gc_mibs_cache(Age) -> {ok, NumElementsGCed} | {error, Reason}</name> + <name since="">gc_mibs_cache(Agent, Age) -> {ok, NumElementsGCed} | {error, Reason}</name> + <name since="">gc_mibs_cache(Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name> + <name since="">gc_mibs_cache(Agent, Age, GcLimit) -> {ok, NumElementsGCed} | {error, Reason}</name> <fsummary>Perform mib server cache gc</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -808,8 +808,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>enable_mibs_cache_autogc() -> void()</name> - <name>enable_mibs_cache_autogc(Agent) -> void()</name> + <name since="">enable_mibs_cache_autogc() -> void()</name> + <name since="">enable_mibs_cache_autogc(Agent) -> void()</name> <fsummary>Enable automatic gc of the mib server cache</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -822,8 +822,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>disable_mibs_cache_autogc() -> void()</name> - <name>disable_mibs_cache_autogc(Agent) -> void()</name> + <name since="">disable_mibs_cache_autogc() -> void()</name> + <name since="">disable_mibs_cache_autogc(Agent) -> void()</name> <fsummary>Disable automatic gc of the mib server cache</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -836,8 +836,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>update_mibs_cache_age(NewAge) -> ok | {error, Reason}</name> - <name>update_mibs_cache_age(Agent, NewAge) -> ok | {error, Reason}</name> + <name since="">update_mibs_cache_age(NewAge) -> ok | {error, Reason}</name> + <name since="">update_mibs_cache_age(Agent, NewAge) -> ok | {error, Reason}</name> <fsummary>Change the mib server cache age property</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -852,8 +852,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>update_mibs_cache_gclimit(NewGcLimit) -> ok | {error, Reason}</name> - <name>update_mibs_cache_gclimit(Agent, NewGCLimit) -> ok | {error, Reason}</name> + <name since="">update_mibs_cache_gclimit(NewGcLimit) -> ok | {error, Reason}</name> + <name since="">update_mibs_cache_gclimit(Agent, NewGCLimit) -> ok | {error, Reason}</name> <fsummary>Change the mib server cache gclimit property</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -869,10 +869,10 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} <func> - <name>register_notification_filter(Id, Mod, Data) -> ok | {error, Reason}</name> - <name>register_notification_filter(Agent, Id, Mod, Data) -> ok | {error, Reason}</name> - <name>register_notification_filter(Id, Mod, Data, Where) -> ok | {error, Reason}</name> - <name>register_notification_filter(Agent, Id, Mod, Data, Where) -> ok | {error, Reason}</name> + <name since="">register_notification_filter(Id, Mod, Data) -> ok | {error, Reason}</name> + <name since="">register_notification_filter(Agent, Id, Mod, Data) -> ok | {error, Reason}</name> + <name since="">register_notification_filter(Id, Mod, Data, Where) -> ok | {error, Reason}</name> + <name since="">register_notification_filter(Agent, Id, Mod, Data, Where) -> ok | {error, Reason}</name> <fsummary>Register a notification filter</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -897,8 +897,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>unregister_notification_filter(Id) -> ok | {error, Reason}</name> - <name>unregister_notification_filter(Agent, Id) -> ok | {error, Reason}</name> + <name since="">unregister_notification_filter(Id) -> ok | {error, Reason}</name> + <name since="">unregister_notification_filter(Agent, Id) -> ok | {error, Reason}</name> <fsummary>Unregister a notification filter</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -913,8 +913,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>which_notification_filter() -> Filters</name> - <name>which_notification_filter(Agent) -> Filters</name> + <name since="">which_notification_filter() -> Filters</name> + <name since="">which_notification_filter(Agent) -> Filters</name> <fsummary>Which notification filter</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -929,8 +929,8 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>set_request_limit(NewLimit) -> {ok, OldLimit} | {error, Reason}</name> - <name>set_request_limit(Agent, NewLimit) -> {ok, OldLimit} | {error, Reason}</name> + <name since="">set_request_limit(NewLimit) -> {ok, OldLimit} | {error, Reason}</name> + <name since="">set_request_limit(Agent, NewLimit) -> {ok, OldLimit} | {error, Reason}</name> <fsummary>Change the request limit</fsummary> <type> <v>NewLimit = OldLimit = infinity | integer() >= 0</v> @@ -950,7 +950,7 @@ notification_delivery_info() = #snmpa_notification_delivery_info{} </func> <func> - <name>register_subagent(Agent, SubTreeOid, Subagent) -> ok | {error, Reason}</name> + <name since="">register_subagent(Agent, SubTreeOid, Subagent) -> ok | {error, Reason}</name> <fsummary>Register a sub-agent under a sub-tree</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -975,7 +975,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>unregister_subagent(Agent, SubagentOidOrPid) -> ok | {ok, SubAgentPid} | {error, Reason}</name> + <name since="">unregister_subagent(Agent, SubagentOidOrPid) -> ok | {ok, SubAgentPid} | {error, Reason}</name> <fsummary>Unregister a sub-agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -992,7 +992,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <func> - <name>send_notification2(Agent, Notification, SendOpts) -> void()</name> + <name since="OTP R14B03">send_notification2(Agent, Notification, SendOpts) -> void()</name> <fsummary>Send notification</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -1119,11 +1119,11 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <func> - <name>send_notification(Agent, Notification, Receiver)</name> - <name>send_notification(Agent, Notification, Receiver, Varbinds)</name> - <name>send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name> - <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name> - <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name> + <name since="">send_notification(Agent, Notification, Receiver)</name> + <name since="">send_notification(Agent, Notification, Receiver, Varbinds)</name> + <name since="">send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name> + <name since="">send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name> + <name since="OTP R14B">send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name> <fsummary>Send a notification</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -1324,13 +1324,13 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). <func> - <name>discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name> - <name>discovery(TargetName, Notification, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name> - <name>discovery(TargetName, Notification, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name> - <name>discovery(TargetName, Notification, ContextName, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name> - <name>discovery(TargetName, Notification, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name> - <name>discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name> - <name>discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification, ContextName, Varbinds) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler) -> {ok, ManagerEngineID} | {error, Reason}</name> + <name since="">discovery(TargetName, Notification, ContextName, Varbinds, DiscoHandler, ExtraInfo) -> {ok, ManagerEngineID} | {error, Reason}</name> <fsummary>Initiate the discovery process with a manager</fsummary> <type> <v>TargetName = string()</v> @@ -1379,7 +1379,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>convert_config(OldConfig) -> AgentConfig</name> + <name since="">convert_config(OldConfig) -> AgentConfig</name> <fsummary>Convert old snmp config to new agent config</fsummary> <type> <v>OldConfig = list()</v> @@ -1403,8 +1403,8 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>restart_worker() -> void()</name> - <name>restart_worker(Agent) -> void()</name> + <name since="">restart_worker() -> void()</name> + <name since="">restart_worker(Agent) -> void()</name> <fsummary>Restart the worker process of a multi-threaded agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -1419,8 +1419,8 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>restart_set_worker() -> void()</name> - <name>restart_set_worker(Agent) -> void()</name> + <name since="">restart_set_worker() -> void()</name> + <name since="">restart_set_worker(Agent) -> void()</name> <fsummary>Restart the set worker process of a multi-threaded agent</fsummary> <type> <v>Agent = pid() | atom()</v> @@ -1435,7 +1435,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>print_mib_info() -> void()</name> + <name since="OTP R14B02">print_mib_info() -> void()</name> <fsummary>Print mib info</fsummary> <desc> <p>Prints the content of all the (snmp) tables and variables @@ -1446,7 +1446,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>print_mib_tables() -> void()</name> + <name since="OTP R14B02">print_mib_tables() -> void()</name> <fsummary>Print mib tables</fsummary> <desc> <p>Prints the content of all the (snmp) tables @@ -1457,7 +1457,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>print_mib_variables() -> void()</name> + <name since="OTP R14B02">print_mib_variables() -> void()</name> <fsummary>Print mib variables</fsummary> <desc> <p>Prints the content of all the (snmp) variables @@ -1468,7 +1468,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2). </func> <func> - <name>verbosity(Ref,Verbosity) -> void()</name> + <name since="">verbosity(Ref,Verbosity) -> void()</name> <fsummary>Assign a new verbosity for the process</fsummary> <type> <v>Ref = pid() | sub_agents | master_agent | net_if | mib_server | symbolic_store | note_store | local_db</v> diff --git a/lib/snmp/doc/src/snmpa_conf.xml b/lib/snmp/doc/src/snmpa_conf.xml index 503e44a6a2..4134a81c0c 100644 --- a/lib/snmp/doc/src/snmpa_conf.xml +++ b/lib/snmp/doc/src/snmpa_conf.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_conf.xml</file> </header> - <module>snmpa_conf</module> + <module since="">snmpa_conf</module> <modulesummary>Utility functions for handling the agent config files.</modulesummary> <description> <p>The module <c>snmpa_conf</c> contains various utility functions to @@ -92,7 +92,7 @@ word() = 0..65535 <funcs> <func> - <name>agent_entry(Tag, Val) -> agent_entry()</name> + <name since="">agent_entry(Tag, Val) -> agent_entry()</name> <fsummary>Create an agent entry</fsummary> <type> <v>Tag = intAgentTransports | intAgentUDPPort | intAgentMaxPacketSize | snmpEngineMaxMessageSize | snmpEngineID</v> @@ -111,8 +111,8 @@ word() = 0..65535 </func> <func> - <name>write_agent_config(Dir, Conf) -> ok</name> - <name>write_agent_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_agent_config(Dir, Conf) -> ok</name> + <name since="">write_agent_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -134,7 +134,7 @@ word() = 0..65535 </func> <func> - <name>append_agent_config(Dir, Conf) -> ok</name> + <name since="">append_agent_config(Dir, Conf) -> ok</name> <fsummary>Append the agent config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -153,7 +153,7 @@ word() = 0..65535 </func> <func> - <name>read_agent_config(Dir) -> Conf</name> + <name since="">read_agent_config(Dir) -> Conf</name> <fsummary>Read the agent config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -172,7 +172,7 @@ word() = 0..65535 </func> <func> - <name>standard_entry(Tag, Val) -> standard_entry()</name> + <name since="">standard_entry(Tag, Val) -> standard_entry()</name> <fsummary>Create an standard entry</fsummary> <type> <v>Tag = sysDescr | sysObjectID | sysContact | sysName | sysLocation | sysServices | snmpEnableAuthenTraps</v> @@ -192,8 +192,8 @@ word() = 0..65535 </func> <func> - <name>write_standard_config(Dir, Conf) -> ok</name> - <name>write_standard_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_standard_config(Dir, Conf) -> ok</name> + <name since="">write_standard_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent standard config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -216,7 +216,7 @@ word() = 0..65535 </func> <func> - <name>append_standard_config(Dir, Conf) -> ok</name> + <name since="">append_standard_config(Dir, Conf) -> ok</name> <fsummary>Append the agent standard config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -236,7 +236,7 @@ word() = 0..65535 </func> <func> - <name>read_standard_config(Dir) -> Conf</name> + <name since="">read_standard_config(Dir) -> Conf</name> <fsummary>Read the agent standard config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -255,7 +255,7 @@ word() = 0..65535 </func> <func> - <name>context_entry(Context) -> context_entry()</name> + <name since="">context_entry(Context) -> context_entry()</name> <fsummary>Create an context entry</fsummary> <type> <v>Context = string()</v> @@ -273,8 +273,8 @@ word() = 0..65535 </func> <func> - <name>write_context_config(Dir, Conf) -> ok</name> - <name>write_context_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_context_config(Dir, Conf) -> ok</name> + <name since="">write_context_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent context(s) to the config file</fsummary> <type> <v>Dir = string()</v> @@ -297,7 +297,7 @@ word() = 0..65535 </func> <func> - <name>append_context_config(Dir, Conf) -> ok</name> + <name since="">append_context_config(Dir, Conf) -> ok</name> <fsummary>Append the agent context(s) to the config file</fsummary> <type> <v>Dir = string()</v> @@ -317,7 +317,7 @@ word() = 0..65535 </func> <func> - <name>read_context_config(Dir) -> Conf</name> + <name since="">read_context_config(Dir) -> Conf</name> <fsummary>Read the agent context config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -336,8 +336,8 @@ word() = 0..65535 </func> <func> - <name>community_entry(CommunityIndex) -> community_entry()</name> - <name>community_entry(CommunityIndex, CommunityName, SecName, ContextName, TransportTag) -> community_entry()</name> + <name since="">community_entry(CommunityIndex) -> community_entry()</name> + <name since="">community_entry(CommunityIndex, CommunityName, SecName, ContextName, TransportTag) -> community_entry()</name> <fsummary>Create an community entry</fsummary> <type> <v>CommunityIndex = string()</v> @@ -364,8 +364,8 @@ word() = 0..65535 </func> <func> - <name>write_community_config(Dir, Conf) -> ok</name> - <name>write_community_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_community_config(Dir, Conf) -> ok</name> + <name since="">write_community_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent community config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -388,7 +388,7 @@ word() = 0..65535 </func> <func> - <name>append_community_config(Dir, Conf) -> ok</name> + <name since="">append_community_config(Dir, Conf) -> ok</name> <fsummary>Append the agent community config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -408,7 +408,7 @@ word() = 0..65535 </func> <func> - <name>read_community_config(Dir) -> Conf</name> + <name since="">read_community_config(Dir) -> Conf</name> <fsummary>Read the agent community config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -427,10 +427,10 @@ word() = 0..65535 </func> <func> - <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> - <name>target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> + <name since="">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId) -> target_addr_entry()</name> + <name since="OTP 17.3">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask) -> target_addr_entry()</name> + <name since="">target_addr_entry(Name, Domain, Addr, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> + <name since="">target_addr_entry(Name, Domain, Addr, Timeout, RetryCount, TagList, ParamsName, EngineId, TMask, MaxMessageSize) -> target_addr_entry()</name> <fsummary>Create an target_addr entry</fsummary> <type> <v>Name = string()</v> @@ -464,8 +464,8 @@ word() = 0..65535 </func> <func> - <name>write_target_addr_config(Dir, Conf) -> ok</name> - <name>write_target_addr_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_target_addr_config(Dir, Conf) -> ok</name> + <name since="">write_target_addr_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent target_addr config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -488,7 +488,7 @@ word() = 0..65535 </func> <func> - <name>append_target_addr_config(Dir, Conf) -> ok</name> + <name since="">append_target_addr_config(Dir, Conf) -> ok</name> <fsummary>Append the agent target_addr config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -508,7 +508,7 @@ word() = 0..65535 </func> <func> - <name>read_target_addr_config(Dir) -> Conf</name> + <name since="">read_target_addr_config(Dir) -> Conf</name> <fsummary>Read the agent target_addr config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -527,9 +527,9 @@ word() = 0..65535 </func> <func> - <name>target_params_entry(Name, Vsn) -> target_params_entry()</name> - <name>target_params_entry(Name, Vsn, SecName, SecLevel) -> target_params_entry()</name> - <name>target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> target_params_entry()</name> + <name since="">target_params_entry(Name, Vsn) -> target_params_entry()</name> + <name since="">target_params_entry(Name, Vsn, SecName, SecLevel) -> target_params_entry()</name> + <name since="">target_params_entry(Name, MPModel, SecModel, SecName, SecLevel) -> target_params_entry()</name> <fsummary>Create an target_params entry</fsummary> <type> <v>Name = string()</v> @@ -564,8 +564,8 @@ word() = 0..65535 </func> <func> - <name>write_target_params_config(Dir, Conf) -> ok</name> - <name>write_target_params_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_target_params_config(Dir, Conf) -> ok</name> + <name since="">write_target_params_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent target_params config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -588,7 +588,7 @@ word() = 0..65535 </func> <func> - <name>append_target_params_config(Dir, Conf) -> ok</name> + <name since="">append_target_params_config(Dir, Conf) -> ok</name> <fsummary>Append the agent target_params config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -608,7 +608,7 @@ word() = 0..65535 </func> <func> - <name>read_target_params_config(Dir) -> Conf</name> + <name since="">read_target_params_config(Dir) -> Conf</name> <fsummary>Read the agent target_params config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -627,10 +627,10 @@ word() = 0..65535 </func> <func> - <name>vacm_s2g_entry(SecModel, SecName, GroupName) -> vacm_s2g_entry()</name> - <name>vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, ReadView, WriteView, NotifyView) -> vacm_acc_entry()</name> - <name>vacm_vtf_entry(ViewIndex, ViewSubtree) -> vacm_vtf_entry()</name> - <name>vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> vacm_vtf_entry()</name> + <name since="">vacm_s2g_entry(SecModel, SecName, GroupName) -> vacm_s2g_entry()</name> + <name since="">vacm_acc_entry(GroupName, Prefix, SecModel, SecLevel, Match, ReadView, WriteView, NotifyView) -> vacm_acc_entry()</name> + <name since="">vacm_vtf_entry(ViewIndex, ViewSubtree) -> vacm_vtf_entry()</name> + <name since="">vacm_vtf_entry(ViewIndex, ViewSubtree, ViewStatus, ViewMask) -> vacm_vtf_entry()</name> <fsummary>Create an vacm entry</fsummary> <type> <v>SecModel = v1 | v2c | usm</v> @@ -665,8 +665,8 @@ word() = 0..65535 </func> <func> - <name>write_vacm_config(Dir, Conf) -> ok</name> - <name>write_vacm_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_vacm_config(Dir, Conf) -> ok</name> + <name since="">write_vacm_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent vacm config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -690,7 +690,7 @@ word() = 0..65535 </func> <func> - <name>append_vacm_config(Dir, Conf) -> ok</name> + <name since="">append_vacm_config(Dir, Conf) -> ok</name> <fsummary>Append the agent vacm config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -710,7 +710,7 @@ word() = 0..65535 </func> <func> - <name>read_vacm_config(Dir) -> Conf</name> + <name since="">read_vacm_config(Dir) -> Conf</name> <fsummary>Read the agent vacm config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -729,8 +729,8 @@ word() = 0..65535 </func> <func> - <name>usm_entry(EngineId) -> usm_entry()</name> - <name>usm_entry(EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> usm_entry()</name> + <name since="">usm_entry(EngineId) -> usm_entry()</name> + <name since="">usm_entry(EngineID, UserName, SecName, Clone, AuthP, AuthKeyC, OwnAuthKeyC, PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey) -> usm_entry()</name> <fsummary>Create an usm entry</fsummary> <type> <v>EngineId = string()</v> @@ -762,8 +762,8 @@ word() = 0..65535 </func> <func> - <name>write_usm_config(Dir, Conf) -> ok</name> - <name>write_usm_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_usm_config(Dir, Conf) -> ok</name> + <name since="">write_usm_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent usm config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -786,7 +786,7 @@ word() = 0..65535 </func> <func> - <name>append_usm_config(Dir, Conf) -> ok</name> + <name since="">append_usm_config(Dir, Conf) -> ok</name> <fsummary>Append the agent usm config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -805,7 +805,7 @@ word() = 0..65535 </func> <func> - <name>read_usm_config(Dir) -> Conf</name> + <name since="">read_usm_config(Dir) -> Conf</name> <fsummary>Read the agent usm config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -824,7 +824,7 @@ word() = 0..65535 </func> <func> - <name>notify_entry(Name, Tag, Type) -> notify_entry()</name> + <name since="">notify_entry(Name, Tag, Type) -> notify_entry()</name> <fsummary>Create an notify entry</fsummary> <type> <v>Name = string()</v> @@ -845,8 +845,8 @@ word() = 0..65535 </func> <func> - <name>write_notify_config(Dir, Conf) -> ok</name> - <name>write_notify_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_notify_config(Dir, Conf) -> ok</name> + <name since="">write_notify_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the agent notify config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -869,7 +869,7 @@ word() = 0..65535 </func> <func> - <name>append_notify_config(Dir, Conf) -> ok</name> + <name since="">append_notify_config(Dir, Conf) -> ok</name> <fsummary>Append the agent notify config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -889,7 +889,7 @@ word() = 0..65535 </func> <func> - <name>read_notify_config(Dir) -> Conf</name> + <name since="">read_notify_config(Dir) -> Conf</name> <fsummary>Read the agent notify config from the config file</fsummary> <type> <v>Dir = string()</v> diff --git a/lib/snmp/doc/src/snmpa_discovery_handler.xml b/lib/snmp/doc/src/snmpa_discovery_handler.xml index 0ea72a880c..21b8746c11 100644 --- a/lib/snmp/doc/src/snmpa_discovery_handler.xml +++ b/lib/snmp/doc/src/snmpa_discovery_handler.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_discovery_handler.xml</file> </header> - <module>snmpa_discovery_handler</module> + <module since="">snmpa_discovery_handler</module> <modulesummary>Behaviour module for the SNMP agent discovery handler.</modulesummary> <description> <p>This module defines the behaviour of the agent discovery @@ -51,7 +51,7 @@ <funcs> <func> - <name>stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> ignore | {ok, usm_entry() | [usm_entry()]} | {ok, usm_entry() | [usm_entry()], NewExtraInfo}</name> + <name since="">stage1_finish(TargetName, ManagerEngineID, ExtraInfo) -> ignore | {ok, usm_entry() | [usm_entry()]} | {ok, usm_entry() | [usm_entry()], NewExtraInfo}</name> <fsummary>Discovery stage 1 finish</fsummary> <type> <v>TargetName = string()</v> diff --git a/lib/snmp/doc/src/snmpa_error.xml b/lib/snmp/doc/src/snmpa_error.xml index 7cc4a3513d..6e6761b7a5 100644 --- a/lib/snmp/doc/src/snmpa_error.xml +++ b/lib/snmp/doc/src/snmpa_error.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_error.xml</file> </header> - <module>snmpa_error</module> + <module since="">snmpa_error</module> <modulesummary>Functions for Reporting SNMP Errors</modulesummary> <description> <marker id="desc"></marker> @@ -57,7 +57,7 @@ </description> <funcs> <func> - <name>config_err(Format, Args) -> void()</name> + <name since="">config_err(Format, Args) -> void()</name> <fsummary>Called if a configuration error occurs</fsummary> <type> <v>Format = string()</v> @@ -76,7 +76,7 @@ </func> <func> - <name>user_err(Format, Args) -> void()</name> + <name since="">user_err(Format, Args) -> void()</name> <fsummary>Called if a user related error occurs</fsummary> <type> <v>Format = string()</v> diff --git a/lib/snmp/doc/src/snmpa_error_io.xml b/lib/snmp/doc/src/snmpa_error_io.xml index bcb2688646..d78e09ff13 100644 --- a/lib/snmp/doc/src/snmpa_error_io.xml +++ b/lib/snmp/doc/src/snmpa_error_io.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_error_io.xml</file> </header> - <module>snmpa_error_io</module> + <module since="">snmpa_error_io</module> <modulesummary>Functions for Reporting SNMP Errors on stdio</modulesummary> <description> <p>The module <c>snmpa_error_io</c> implements the @@ -52,7 +52,7 @@ </description> <funcs> <func> - <name>config_err(Format, Args) -> void()</name> + <name since="">config_err(Format, Args) -> void()</name> <fsummary>Called if a configuration error occurs</fsummary> <type> <v>Format = string()</v> @@ -68,7 +68,7 @@ </desc> </func> <func> - <name>user_err(Format, Args) -> void()</name> + <name since="">user_err(Format, Args) -> void()</name> <fsummary>Called if a user related error occurs</fsummary> <type> <v>Format = string()</v> diff --git a/lib/snmp/doc/src/snmpa_error_logger.xml b/lib/snmp/doc/src/snmpa_error_logger.xml index 4feb2e7f32..b0565a6839 100644 --- a/lib/snmp/doc/src/snmpa_error_logger.xml +++ b/lib/snmp/doc/src/snmpa_error_logger.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_error_logger.xml</file> </header> - <module>snmpa_error_logger</module> + <module since="">snmpa_error_logger</module> <modulesummary>Functions for Reporting SNMP Errors through the error_logger</modulesummary> <description> <p>The module <c>snmpa_error_logger</c> implements the @@ -54,7 +54,7 @@ </description> <funcs> <func> - <name>config_err(Format, Args) -> void()</name> + <name since="">config_err(Format, Args) -> void()</name> <fsummary>Called if a configuration error occurs</fsummary> <type> <v>Format = string()</v> @@ -70,7 +70,7 @@ </desc> </func> <func> - <name>user_err(Format, Args) -> void()</name> + <name since="">user_err(Format, Args) -> void()</name> <fsummary>Called if a user related error occurs</fsummary> <type> <v>Format = string()</v> diff --git a/lib/snmp/doc/src/snmpa_error_report.xml b/lib/snmp/doc/src/snmpa_error_report.xml index 282d9b4e59..f08dc1df23 100644 --- a/lib/snmp/doc/src/snmpa_error_report.xml +++ b/lib/snmp/doc/src/snmpa_error_report.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_error_report.xml</file> </header> - <module>snmpa_error_report</module> + <module since="">snmpa_error_report</module> <modulesummary>Behaviour module for reporting SNMP agent errors</modulesummary> <description> <marker id="desc"></marker> @@ -52,7 +52,7 @@ </description> <funcs> <func> - <name>config_err(Format, Args) -> void()</name> + <name since="">config_err(Format, Args) -> void()</name> <fsummary>Called if a configuration error occurs</fsummary> <type> <v>Format = string()</v> @@ -68,7 +68,7 @@ </desc> </func> <func> - <name>user_err(Format, Args) -> void()</name> + <name since="">user_err(Format, Args) -> void()</name> <fsummary>Called if a user related error occurs</fsummary> <type> <v>Format = string()</v> diff --git a/lib/snmp/doc/src/snmpa_local_db.xml b/lib/snmp/doc/src/snmpa_local_db.xml index ac8d466ab3..229f22ab70 100644 --- a/lib/snmp/doc/src/snmpa_local_db.xml +++ b/lib/snmp/doc/src/snmpa_local_db.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_local_db.xml</file> </header> - <module>snmpa_local_db</module> + <module since="">snmpa_local_db</module> <modulesummary>The SNMP built-in database</modulesummary> <description> <p>The module <c>snmpa_local_db</c> contains functions for @@ -86,7 +86,7 @@ </section> <funcs> <func> - <name>dump() -> ok | {error, Reason}</name> + <name since="">dump() -> ok | {error, Reason}</name> <fsummary>Dump the database to disk</fsummary> <type> <v>Reason = term()</v> @@ -97,7 +97,7 @@ </desc> </func> <func> - <name>match(NameDb, Pattern)</name> + <name since="">match(NameDb, Pattern)</name> <fsummary>Perform a match on the table</fsummary> <desc> <p>Performs an ets/dets matching on the table. @@ -106,9 +106,9 @@ </desc> </func> <func> - <name>print()</name> - <name>print(TableName)</name> - <name>print(TableName, Db)</name> + <name since="">print()</name> + <name since="">print(TableName)</name> + <name since="">print(TableName, Db)</name> <fsummary>Print the database to screen</fsummary> <type> <v>TableName = atom()</v> @@ -124,7 +124,7 @@ </desc> </func> <func> - <name>table_create(NameDb) -> bool()</name> + <name since="">table_create(NameDb) -> bool()</name> <fsummary>Create a table</fsummary> <desc> <p>Creates a table. If the table already exist, the old copy @@ -135,7 +135,7 @@ </desc> </func> <func> - <name>table_create_row(NameDb, RowIndex, Row) -> bool()</name> + <name since="">table_create_row(NameDb, RowIndex, Row) -> bool()</name> <fsummary>Create a row in a table</fsummary> <type> <v>Row = {Val1, Val2, ..., ValN}</v> @@ -147,28 +147,28 @@ </desc> </func> <func> - <name>table_delete(NameDb) -> void()</name> + <name since="">table_delete(NameDb) -> void()</name> <fsummary>Delete a table</fsummary> <desc> <p>Deletes a table.</p> </desc> </func> <func> - <name>table_delete_row(NameDb, RowIndex) -> bool()</name> + <name since="">table_delete_row(NameDb, RowIndex) -> bool()</name> <fsummary>Delete the row in the table</fsummary> <desc> <p>Deletes the row in the table.</p> </desc> </func> <func> - <name>table_exists(NameDb) -> bool()</name> + <name since="">table_exists(NameDb) -> bool()</name> <fsummary>Check if a table exists</fsummary> <desc> <p>Checks if a table exists.</p> </desc> </func> <func> - <name>table_get_row(NameDb, RowIndex) -> Row | undefined</name> + <name since="">table_get_row(NameDb, RowIndex) -> Row | undefined</name> <fsummary>Get a row from the table</fsummary> <type> <v>Row = {Val1, Val2, ..., ValN}</v> diff --git a/lib/snmp/doc/src/snmpa_mib_data.xml b/lib/snmp/doc/src/snmpa_mib_data.xml index 1a73c9b634..b849a2826a 100644 --- a/lib/snmp/doc/src/snmpa_mib_data.xml +++ b/lib/snmp/doc/src/snmpa_mib_data.xml @@ -30,7 +30,7 @@ <file>snmpa_mib_data.xml</file> </header> - <module>snmpa_mib_data</module> + <module since="OTP R16B01">snmpa_mib_data</module> <modulesummary>Behaviour module for the SNMP agent mib-server data module.</modulesummary> <description> @@ -108,7 +108,7 @@ <funcs> <func> - <name>Module:new(Storage) -> State</name> + <name since="OTP R16B01">Module:new(Storage) -> State</name> <fsummary>Create new (mib-server) data instance</fsummary> <type> <v>Storage = mib_storage()</v> @@ -122,7 +122,7 @@ </func> <func> - <name>Module:close(State) -> void()</name> + <name since="OTP R16B01">Module:close(State) -> void()</name> <fsummary>Close the mib-server data instance</fsummary> <type> <v>State = term()</v> @@ -135,7 +135,7 @@ </func> <func> - <name>Module:sync(State) -> void()</name> + <name since="OTP R16B01">Module:sync(State) -> void()</name> <fsummary>Synchronize to disc</fsummary> <type> <v>State = term()</v> @@ -151,7 +151,7 @@ </func> <func> - <name>Module:load_mib(State, Filename, MeOverride, TeOverride) -> {ok, NewState} | {error, Reason}</name> + <name since="OTP R16B01">Module:load_mib(State, Filename, MeOverride, TeOverride) -> {ok, NewState} | {error, Reason}</name> <fsummary>Load a mib into the mib-server</fsummary> <type> <v>State = NewState = term()</v> @@ -172,7 +172,7 @@ </func> <func> - <name>Module:unload_mib(State, Filename) -> {ok, NewState} | {error, Reason}</name> + <name since="OTP R16B01">Module:unload_mib(State, Filename) -> {ok, NewState} | {error, Reason}</name> <fsummary>Unload mib from the mib-server</fsummary> <type> <v>State = NewState = term()</v> @@ -188,7 +188,7 @@ </func> <func> - <name>Module:lookup(State, Oid) -> Reply</name> + <name since="OTP R16B01">Module:lookup(State, Oid) -> Reply</name> <fsummary>Find the mib-entry corresponding to the Oid</fsummary> <type> <v>State = term()</v> @@ -210,7 +210,7 @@ </func> <func> - <name>Module:next(State, Oid, MibView) -> Reply</name> + <name since="OTP R16B01">Module:next(State, Oid, MibView) -> Reply</name> <fsummary>Finds the lexicographically next oid</fsummary> <type> <v>State = term()</v> @@ -227,7 +227,7 @@ </func> <func> - <name>Module:register_subagent(State, Oid, Pid) -> Reply</name> + <name since="OTP R16B01">Module:register_subagent(State, Oid, Pid) -> Reply</name> <fsummary>Register the subagent</fsummary> <type> <v>State = NewState = term()</v> @@ -245,7 +245,7 @@ </func> <func> - <name>Module:unregister_subagent(State, PidOrOid) -> Reply</name> + <name since="OTP R16B01">Module:unregister_subagent(State, PidOrOid) -> Reply</name> <fsummary>Unregister the subagent</fsummary> <type> <v>State = NewState = term()</v> @@ -266,7 +266,7 @@ </func> <func> - <name>Module:dump(State, Destination) -> Reply</name> + <name since="OTP R16B01">Module:dump(State, Destination) -> Reply</name> <fsummary>Unregister the subagent</fsummary> <type> <v>State = term()</v> @@ -284,7 +284,7 @@ </func> <func> - <name>Module:which_mib(State, Oid) -> Reply</name> + <name since="OTP R16B01">Module:which_mib(State, Oid) -> Reply</name> <fsummary>Retrieve the mib file for an oid()</fsummary> <type> <v>State = term()</v> @@ -301,7 +301,7 @@ </func> <func> - <name>Module:which_mibs(State) -> Reply</name> + <name since="OTP R16B01">Module:which_mibs(State) -> Reply</name> <fsummary>Retrieve all loaded mib files</fsummary> <type> <v>State = term()</v> @@ -317,7 +317,7 @@ </func> <func> - <name>Module:whereis_mib(State, MibName) -> Reply</name> + <name since="OTP R16B01">Module:whereis_mib(State, MibName) -> Reply</name> <fsummary>Retrieve the mib file for the mib</fsummary> <type> <v>State = term()</v> @@ -334,7 +334,7 @@ </func> <func> - <name>Module:info(State) -> Reply</name> + <name since="OTP R16B01">Module:info(State) -> Reply</name> <fsummary>Retrieve misc info for the mib data</fsummary> <type> <v>State = term()</v> @@ -352,7 +352,7 @@ </func> <func> - <name>Module:backup(State, BackupDir) -> Reply</name> + <name since="OTP R16B01">Module:backup(State, BackupDir) -> Reply</name> <fsummary>Perform a backup of the mib-server data</fsummary> <type> <v>State = term()</v> @@ -370,7 +370,7 @@ </func> <func> - <name>Module:code_change(Destination, Vsn, Extra, State) -> NewState</name> + <name since="OTP R16B01">Module:code_change(Destination, Vsn, Extra, State) -> NewState</name> <fsummary>Perform a code-change</fsummary> <type> <v>Destination = up | down</v> diff --git a/lib/snmp/doc/src/snmpa_mib_storage.xml b/lib/snmp/doc/src/snmpa_mib_storage.xml index 58ce2167ec..ee2b009e77 100644 --- a/lib/snmp/doc/src/snmpa_mib_storage.xml +++ b/lib/snmp/doc/src/snmpa_mib_storage.xml @@ -30,7 +30,7 @@ <file>snmpa_mib_storage.xml</file> </header> - <module>snmpa_mib_storage</module> + <module since="OTP R16B01">snmpa_mib_storage</module> <modulesummary> Behaviour module for the SNMP agent mib storage. </modulesummary> @@ -96,7 +96,7 @@ <funcs> <func> - <name>Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name> + <name since="OTP R16B01">Module:open(Name, RecordName, Fields, Type, Options) -> {ok, TabId} | {error, Reason}</name> <fsummary>Create new (mib-server) data instance</fsummary> <type> <v>Name = atom()</v> @@ -122,7 +122,7 @@ </func> <func> - <name>Module:close(TabId) -> void()</name> + <name since="OTP R16B01">Module:close(TabId) -> void()</name> <fsummary>Close the mib-storage table</fsummary> <type> <v>State = term()</v> @@ -135,7 +135,7 @@ </func> <func> - <name>Module:read(TabId, Key) -> false | {value, Record}</name> + <name since="OTP R16B01">Module:read(TabId, Key) -> false | {value, Record}</name> <fsummary>Read a record from the mib-storage table</fsummary> <type> <v>TabId = term()</v> @@ -150,7 +150,7 @@ </func> <func> - <name>Module:write(TabId, Record) -> ok | {error, Reason}</name> + <name since="OTP R16B01">Module:write(TabId, Record) -> ok | {error, Reason}</name> <fsummary>Write a record to the mib-storage table</fsummary> <type> <v>TabId = term()</v> @@ -165,7 +165,7 @@ </func> <func> - <name>Module:delete(TabId) -> void()</name> + <name since="OTP R16B01">Module:delete(TabId) -> void()</name> <fsummary>Delete an entire mib-storage table</fsummary> <type> <v>TabId = term()</v> @@ -178,7 +178,7 @@ </func> <func> - <name>Module:delete(TabId, Key) -> ok | {error, Reason}</name> + <name since="OTP R16B01">Module:delete(TabId, Key) -> ok | {error, Reason}</name> <fsummary>Delete a record from the mib-storage table</fsummary> <type> <v>TabId = term()</v> @@ -193,7 +193,7 @@ </func> <func> - <name>Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name> + <name since="OTP R16B01">Module:match_object(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name> <fsummary>Search the mib-storage table for record matching pattern</fsummary> <type> <v>TabId = term()</v> @@ -210,7 +210,7 @@ </func> <func> - <name>Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name> + <name since="OTP R16B01">Module:match_delete(TabId, Pattern) -> {ok, Recs} | {error, Reason}</name> <fsummary>Delete records in the mib-storage table matching pattern</fsummary> <type> <v>TabId = term()</v> @@ -228,7 +228,7 @@ </func> <func> - <name>Module:tab2list(TabId) -> Recs</name> + <name since="OTP R16B01">Module:tab2list(TabId) -> Recs</name> <fsummary>Return all records of the mib-storage table</fsummary> <type> <v>TabId = term()</v> @@ -243,7 +243,7 @@ </func> <func> - <name>Module:info(TabId) -> {ok, Info} | {error, Reason}</name> + <name since="OTP R16B01">Module:info(TabId) -> {ok, Info} | {error, Reason}</name> <fsummary>Returns information about the mib-storage table. </fsummary> <type> <v>TabId = term()</v> @@ -259,7 +259,7 @@ </func> <func> - <name>Module:sync(TabId) -> void()</name> + <name since="OTP R16B01">Module:sync(TabId) -> void()</name> <fsummary>Synchronize mib-storage table</fsummary> <type> <v>TabId = term()</v> @@ -273,7 +273,7 @@ </func> <func> - <name>Module:backup(TabId, BackupDir) -> ok | {error, Reason}</name> + <name since="OTP R16B01">Module:backup(TabId, BackupDir) -> ok | {error, Reason}</name> <fsummary>Perform a backup of the mib-storage table</fsummary> <type> <v>TabId = term()</v> diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml index a39c087c20..d76a9c4a94 100644 --- a/lib/snmp/doc/src/snmpa_mpd.xml +++ b/lib/snmp/doc/src/snmpa_mpd.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_mpd.xml</file> </header> - <module>snmpa_mpd</module> + <module since="">snmpa_mpd</module> <modulesummary>Message Processing and Dispatch module for the SNMP agent</modulesummary> <description> <p>The module <c>snmpa_mpd</c> implements the version independent @@ -52,7 +52,7 @@ <funcs> <func> - <name>init(Vsns) -> mpd_state()</name> + <name since="">init(Vsns) -> mpd_state()</name> <fsummary>Initialize the MPD module</fsummary> <type> <v>Vsns = [Vsn]</v> @@ -70,8 +70,8 @@ </func> <func> - <name>process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> - <name>process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> + <name since="OTP 17.3">process_packet(Packet, From, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> + <name since="OTP R14B">process_packet(Packet, From, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name> <fsummary>Process a packet received from the network</fsummary> <type> <v>Packet = binary()</v> @@ -108,8 +108,8 @@ </func> <func> - <name>generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name> - <name>generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name> + <name since="OTP R14B">generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name> + <name since="OTP R14B">generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name> <fsummary>Generate a response packet to be sent to the network</fsummary> <type> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> @@ -136,8 +136,8 @@ </func> <func> - <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> - <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> + <name since="OTP R14B">generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> + <name since="OTP R14B">generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name> <fsummary>Generate a request message to be sent to the network</fsummary> <type> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> @@ -185,7 +185,7 @@ </func> <func> - <name>process_taddrs(TDests) -> Dests</name> + <name since="OTP 17.3">process_taddrs(TDests) -> Dests</name> <fsummary>Transform addresses from internal MIB format to a less internal </fsummary> <type> @@ -211,7 +211,7 @@ </func> <func> - <name>discarded_pdu(Variable) -> void()</name> + <name since="">discarded_pdu(Variable) -> void()</name> <fsummary>Increment the variable associated with a discarded pdu</fsummary> <type> <v>Variable = atom()</v> diff --git a/lib/snmp/doc/src/snmpa_network_interface.xml b/lib/snmp/doc/src/snmpa_network_interface.xml index d4d4989e90..3e79df11b1 100644 --- a/lib/snmp/doc/src/snmpa_network_interface.xml +++ b/lib/snmp/doc/src/snmpa_network_interface.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_network_interface.xml</file> </header> - <module>snmpa_network_interface</module> + <module since="">snmpa_network_interface</module> <modulesummary>Behaviour module for the SNMP agent network interface.</modulesummary> <description> <p>This module defines the behaviour of the agent network @@ -68,7 +68,7 @@ <funcs> <func> - <name>start_link(Prio, NoteStore, MasterAgent, Opts) -> {ok, Pid} | {error, Reason}</name> + <name since="">start_link(Prio, NoteStore, MasterAgent, Opts) -> {ok, Pid} | {error, Reason}</name> <fsummary>Start-link the network interface process</fsummary> <type> <v>Prio = priority()</v> @@ -93,7 +93,7 @@ </func> <func> - <name>info(Pid) -> [{Key, Value}]</name> + <name since="">info(Pid) -> [{Key, Value}]</name> <fsummary>Return information about the running network interface process</fsummary> <type> <v>Pid = pid()</v> @@ -112,7 +112,7 @@ </func> <func> - <name>verbosity(Pid, Verbosity) -> void()</name> + <name since="">verbosity(Pid, Verbosity) -> void()</name> <fsummary>Change the verbosity of a running network interface process</fsummary> <type> <v>Pid = pid()</v> @@ -126,7 +126,7 @@ </func> <func> - <name>get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name> + <name since="">get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name> <fsummary>Get the Audit Trail Log type</fsummary> <type> <v>Pid = pid()</v> @@ -147,7 +147,7 @@ </func> <func> - <name>set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name> + <name since="">set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name> <fsummary>Change the Audit Trail Log type</fsummary> <type> <v>Pid = pid()</v> diff --git a/lib/snmp/doc/src/snmpa_network_interface_filter.xml b/lib/snmp/doc/src/snmpa_network_interface_filter.xml index 7cd08f8935..02c7d291dd 100644 --- a/lib/snmp/doc/src/snmpa_network_interface_filter.xml +++ b/lib/snmp/doc/src/snmpa_network_interface_filter.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_network_interface_filter.xml</file> </header> - <module>snmpa_network_interface_filter</module> + <module since="">snmpa_network_interface_filter</module> <modulesummary>Behaviour module for the SNMP agent network-interface filter.</modulesummary> <description> <p>This module defines the behaviour of the agent network interface @@ -101,7 +101,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </section> <funcs> <func> - <name>accept_recv(Domain, Addr) -> boolean()</name> + <name since="">accept_recv(Domain, Addr) -> boolean()</name> <fsummary>Shall the received message be accepted</fsummary> <type> <v>Domain = transportDomain()</v> @@ -116,7 +116,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </desc> </func> <func> - <name>accept_send(Domain, Addr) -> boolean()</name> + <name since="">accept_send(Domain, Addr) -> boolean()</name> <fsummary>Shall the message be sent</fsummary> <type> <v>Domain = transportDomain()</v> @@ -131,7 +131,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </desc> </func> <func> - <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name> + <name since="">accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name> <fsummary>Shall the received pdu be accepted</fsummary> <type> <v>Domain = transportDomain()</v> @@ -148,7 +148,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </desc> </func> <func> - <name>accept_send_pdu(Targets, PduType) -> Reply</name> + <name since="">accept_send_pdu(Targets, PduType) -> Reply</name> <fsummary>Shall the pdu be sent</fsummary> <type> <v>Targets = targets()</v> diff --git a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml index cbae158544..5dad372710 100644 --- a/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml +++ b/lib/snmp/doc/src/snmpa_notification_delivery_info_receiver.xml @@ -34,7 +34,7 @@ <rev></rev> <file>snmpa_notification_delivery_info_receiver.xml</file> </header> - <module>snmpa_notification_delivery_info_receiver</module> + <module since="">snmpa_notification_delivery_info_receiver</module> <modulesummary> Behaviour module for the SNMP agent notification delivery information receiver. @@ -76,7 +76,7 @@ <funcs> <func> - <name>delivery_targets(Tag, Targets, Extra) -> void()</name> + <name since="">delivery_targets(Tag, Targets, Extra) -> void()</name> <fsummary>Inform about target addresses</fsummary> <type> <v>Tag = term()</v> @@ -97,7 +97,7 @@ </func> <func> - <name>delivery_info(Tag, Target, DeliveryResult, Extra) -> void()</name> + <name since="">delivery_info(Tag, Target, DeliveryResult, Extra) -> void()</name> <fsummary>Inform about delivery result</fsummary> <type> <v>Tag = term()</v> diff --git a/lib/snmp/doc/src/snmpa_notification_filter.xml b/lib/snmp/doc/src/snmpa_notification_filter.xml index 0f16ba4440..902412ccc5 100644 --- a/lib/snmp/doc/src/snmpa_notification_filter.xml +++ b/lib/snmp/doc/src/snmpa_notification_filter.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_notification_filter.xml</file> </header> - <module>snmpa_notification_filter</module> + <module since="">snmpa_notification_filter</module> <modulesummary>Behaviour module for the SNMP agent notification filters.</modulesummary> <description> <p>This module defines the behaviour of the agent notification @@ -51,7 +51,7 @@ </description> <funcs> <func> - <name>handle_notification(Notif, Data) -> Reply</name> + <name since="">handle_notification(Notif, Data) -> Reply</name> <fsummary>Handle a notification</fsummary> <type> <v>Reply = send | {send, NewNotif} | dont_send</v> diff --git a/lib/snmp/doc/src/snmpa_supervisor.xml b/lib/snmp/doc/src/snmpa_supervisor.xml index 86c6fbc350..e11cde390f 100644 --- a/lib/snmp/doc/src/snmpa_supervisor.xml +++ b/lib/snmp/doc/src/snmpa_supervisor.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpa_supervisor.xml</file> </header> - <module>snmpa_supervisor</module> + <module since="">snmpa_supervisor</module> <modulesummary>A supervisor for the SNMP agent Processes</modulesummary> <description> <p>This is the top supervisor for the agent part of the SNMP @@ -42,7 +42,7 @@ </description> <funcs> <func> - <name>start_sub_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name> + <name since="">start_sub_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name> <fsummary>Start the SNMP supervisor for sub-agents only</fsummary> <type> <v>Opts = [opt()]</v> @@ -60,7 +60,7 @@ </desc> </func> <func> - <name>start_master_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name> + <name since="">start_master_sup(Opts) -> {ok, pid()} | {error, {already_started, pid()}} | {error, Reason}</name> <fsummary>Start the SNMP supervisor for all agents</fsummary> <type> <v>Opts = [opt()]</v> @@ -82,7 +82,7 @@ </desc> </func> <func> - <name>start_sub_agent(ParentAgent,Subtree,Mibs) -> {ok, pid()} | {error, Reason}</name> + <name since="">start_sub_agent(ParentAgent,Subtree,Mibs) -> {ok, pid()} | {error, Reason}</name> <fsummary>Start a sub-agent</fsummary> <type> <v>ParentAgent = pid()</v> @@ -99,7 +99,7 @@ </desc> </func> <func> - <name>stop_sub_agent(SubAgent) -> ok | no_such_child</name> + <name since="">stop_sub_agent(SubAgent) -> ok | no_such_child</name> <fsummary>Stop a sub-agent</fsummary> <type> <v>SubAgent = pid()</v> diff --git a/lib/snmp/doc/src/snmpc.xml b/lib/snmp/doc/src/snmpc.xml index aba51bb500..b22b32a133 100644 --- a/lib/snmp/doc/src/snmpc.xml +++ b/lib/snmp/doc/src/snmpc.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpc.xml</file> </header> - <module>snmpc</module> + <module since="">snmpc</module> <modulesummary>Interface Functions to the SNMP toolkit MIB compiler</modulesummary> <description> <p>The module <c>snmpc</c> contains interface functions to the @@ -43,8 +43,8 @@ <funcs> <func> - <name>compile(File)</name> - <name>compile(File, Options) -> {ok, BinFileName} | {error, Reason}</name> + <name since="">compile(File)</name> + <name since="">compile(File, Options) -> {ok, BinFileName} | {error, Reason}</name> <fsummary>Compile the specified MIB</fsummary> <type> <v>File = string()</v> @@ -236,7 +236,7 @@ </func> <func> - <name>is_consistent(Mibs) -> ok | {error, Reason}</name> + <name since="">is_consistent(Mibs) -> ok | {error, Reason}</name> <fsummary>Check for OID conflicts between MIBs</fsummary> <type> <v>Mibs = [MibName]</v> @@ -252,7 +252,7 @@ </func> <func> - <name>mib_to_hrl(MibName) -> ok | {error, Reason}</name> + <name since="">mib_to_hrl(MibName) -> ok | {error, Reason}</name> <fsummary>Generate constants for the objects in the MIB</fsummary> <type> <v>MibName = string()</v> diff --git a/lib/snmp/doc/src/snmpm.xml b/lib/snmp/doc/src/snmpm.xml index be4cd58a1a..c45df98ee0 100644 --- a/lib/snmp/doc/src/snmpm.xml +++ b/lib/snmp/doc/src/snmpm.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpm.xml</file> </header> - <module>snmpm</module> + <module since="">snmpm</module> <modulesummary>Interface functions to the SNMP toolkit manager</modulesummary> <description> <p>The module <c>snmpm</c> contains interface functions to the @@ -77,7 +77,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv </section> <funcs> <func> - <name>monitor() -> Ref</name> + <name since="">monitor() -> Ref</name> <fsummary>Monitor the snmp manager</fsummary> <type> <v>Ref = reference()</v> @@ -92,7 +92,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>demonitor(Ref) -> void()</name> + <name since="">demonitor(Ref) -> void()</name> <fsummary>Turn off monitoring of the snmp manager</fsummary> <type> <v>Ref = reference()</v> @@ -105,7 +105,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>notify_started(Timeout) -> Pid</name> + <name since="">notify_started(Timeout) -> Pid</name> <fsummary>Request to be notified when manager started</fsummary> <type> <v>Timeout = integer()</v> @@ -148,7 +148,7 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>cancel_notify_started(Pid) -> void()</name> + <name since="">cancel_notify_started(Pid) -> void()</name> <fsummary>Cancel request to be notified when manager started</fsummary> <type> <v>Pid = pid()</v> @@ -161,8 +161,8 @@ sec_level() = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>register_user(Id, Module, Data) -> ok | {error, Reason}</name> - <name>register_user(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name> + <name since="">register_user(Id, Module, Data) -> ok | {error, Reason}</name> + <name since="">register_user(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name> <fsummary>Register a user of the manager</fsummary> <type> <v>Id = term()</v> @@ -204,8 +204,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>register_user_monitor(Id, Module, Data) -> ok | {error, Reason}</name> - <name>register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name> + <name since="">register_user_monitor(Id, Module, Data) -> ok | {error, Reason}</name> + <name since="">register_user_monitor(Id, Module, Data, DefaultAgentConfig) -> ok | {error, Reason}</name> <fsummary>Register a monitored user of the manager</fsummary> <type> <v>Id = term()</v> @@ -252,7 +252,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>unregister_user(Id) -> ok | {error, Reason}</name> + <name since="">unregister_user(Id) -> ok | {error, Reason}</name> <fsummary>Unregister the user</fsummary> <type> <v>Id = term()</v> @@ -265,7 +265,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>which_users() -> Users</name> + <name since="">which_users() -> Users</name> <fsummary>Get a list of all users</fsummary> <type> <v>Users = [UserId]</v> @@ -279,7 +279,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>register_agent(UserId, TargetName, Config) -> ok | {error, Reason}</name> + <name since="">register_agent(UserId, TargetName, Config) -> ok | {error, Reason}</name> <fsummary>Register this agent</fsummary> <type> <v>UserId = term()</v> @@ -325,7 +325,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>unregister_agent(UserId, TargetName) -> ok | {error, Reason}</name> + <name since="">unregister_agent(UserId, TargetName) -> ok | {error, Reason}</name> <fsummary>Unregister the user</fsummary> <type> <v>UserId = term()</v> @@ -339,7 +339,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>agent_info(TargetName, Item) -> {ok, Val} | {error, Reason}</name> + <name since="">agent_info(TargetName, Item) -> {ok, Val} | {error, Reason}</name> <fsummary>Retrieve agent config</fsummary> <type> <v>TargetName = target_name()</v> @@ -354,8 +354,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name> - <name>update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name> + <name since="OTP R14B04">update_agent_info(UserId, TargetName, Info) -> ok | {error, Reason}</name> + <name since="">update_agent_info(UserId, TargetName, Item, Val) -> ok | {error, Reason}</name> <fsummary>Update agent config</fsummary> <type> <v>UserId = term()</v> @@ -379,8 +379,8 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>which_agents() -> Agents</name> - <name>which_agents(UserId) -> Agents</name> + <name since="">which_agents() -> Agents</name> + <name since="">which_agents(UserId) -> Agents</name> <fsummary>List the registered agents</fsummary> <type> <v>UserId = term()</v> @@ -396,7 +396,7 @@ sec_level = noAuthNoPriv | authNoPriv | authPriv </func> <func> - <name>register_usm_user(EngineID, UserName, Conf) -> ok | {error, Reason}</name> + <name since="">register_usm_user(EngineID, UserName, Conf) -> ok | {error, Reason}</name> <fsummary>Register this USM user</fsummary> <type> <v>EngineID = string()</v> @@ -427,7 +427,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>unregister_usm_user(EngineID, UserName) -> ok | {error, Reason}</name> + <name since="">unregister_usm_user(EngineID, UserName) -> ok | {error, Reason}</name> <fsummary>Unregister this USM user</fsummary> <type> <v>EngineID = string()</v> @@ -442,7 +442,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>usm_user_info(EngineID, UserName, Item) -> {ok, Val} | {error, Reason}</name> + <name since="">usm_user_info(EngineID, UserName, Item) -> {ok, Val} | {error, Reason}</name> <fsummary>Retrieve usm user config</fsummary> <type> <v>EngineID = string()</v> @@ -458,7 +458,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>update_usm_user_info(EngineID, UserName, Item, Val) -> ok | {error, Reason}</name> + <name since="">update_usm_user_info(EngineID, UserName, Item, Val) -> ok | {error, Reason}</name> <fsummary>Update agent config</fsummary> <type> <v>EngineID = string()</v> @@ -475,7 +475,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>which_usm_users() -> UsmUsers</name> + <name since="">which_usm_users() -> UsmUsers</name> <fsummary>List all the registered usm users</fsummary> <type> <v>UsmUsers = [{EngineID,UserName}]</v> @@ -490,7 +490,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>which_usm_users(EngineID) -> UsmUsers</name> + <name since="">which_usm_users(EngineID) -> UsmUsers</name> <fsummary>List the registered usm users</fsummary> <type> <v>UsmUsers = [UserName]</v> @@ -505,8 +505,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_get2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_get2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_get2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>get-request</c></fsummary> <type> <v>UserId = term()</v> @@ -559,11 +559,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>get-request</c></fsummary> <type> <v>UserId = term()</v> @@ -605,8 +605,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_get2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_get2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>get-request</c></fsummary> <type> <v>UserId = term()</v> @@ -647,11 +647,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>get-request</c></fsummary> <type> <v>UserId = term()</v> @@ -683,8 +683,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>get-next-request</c></fsummary> <type> <v>UserId = term()</v> @@ -737,11 +737,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_next(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_next(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_next(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_next(UserId, TargetName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_next(UserId, TargetName, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_next(UserId, TargetName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_next(UserId, TargetName, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_next(UserId, TargetName, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>get-next-request</c></fsummary> <type> <v>UserId = term()</v> @@ -775,8 +775,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_get_next2(UserId, TargetName, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>get-next-request</c></fsummary> <type> <v>UserId = term()</v> @@ -814,11 +814,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_next(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_next(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_next(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_next(UserId, TargetName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_next(UserId, TargetName, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_next(UserId, TargetName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_next(UserId, TargetName, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_next(UserId, TargetName, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>get-next-request</c></fsummary> <type> <v>UserId = term()</v> @@ -849,8 +849,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>set-request</c></fsummary> <type> <v>UserId = term()</v> @@ -906,11 +906,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_set(UserId, TargetName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_set(UserId, TargetName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_set(UserId, TargetName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_set(UserId, TargetName, ContextName, VarsAndVals, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>set-request</c></fsummary> <type> <v>UserId = term()</v> @@ -946,8 +946,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name> - <name>async_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_set2(UserId, TargetName, VarsAndVals, SendOpts) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>set-request</c></fsummary> <type> <v>UserId = term()</v> @@ -990,11 +990,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name> - <name>async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name> - <name>async_set(UserId, TargetName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_set(UserId, TargetName, ContextName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_set(UserId, TargetName, ContextName, VarsAndVals, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_set(UserId, TargetName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_set(UserId, TargetName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_set(UserId, TargetName, ContextName, VarsAndVals, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>set-request</c></fsummary> <type> <v>UserId = term()</v> @@ -1026,8 +1026,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="OTP R14B03">sync_get_bulk2(UserId, TragetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>get-bulk-request</c></fsummary> <type> <v>UserId = term()</v> @@ -1082,11 +1082,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> - <name>sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> + <name since="">sync_get_bulk(UserId, TragetName, NonRep, MaxRep, ContextName, Oids, Timeout, ExtraInfo) -> {ok, SnmpReply, Remaining} | {error, Reason}</name> <fsummary>Synchronous <c>get-bulk-request</c></fsummary> <type> <v>UserId = term()</v> @@ -1121,8 +1121,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="OTP R14B03">async_get_bulk2(UserId, TargetName, NonRep, MaxRep, Oids, SendOpts) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>get-bulk-request</c></fsummary> <type> <v>UserId = term()</v> @@ -1162,11 +1162,11 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> - <name>async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire) -> {ok, ReqId} | {error, Reason}</name> + <name since="">async_get_bulk(UserId, TargetName, NonRep, MaxRep, ContextName, Oids, Expire, ExtraInfo) -> {ok, ReqId} | {error, Reason}</name> <fsummary>Asynchronous <c>get-bulk-request</c></fsummary> <type> <v>UserId = term()</v> @@ -1199,7 +1199,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name> + <name since="">cancel_async_request(UserId, ReqId) -> ok | {error, Reason}</name> <fsummary>Cancel a asynchronous request</fsummary> <type> <v>UserId = term()</v> @@ -1214,15 +1214,15 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>log_to_txt(LogDir)</name> - <name>log_to_txt(LogDir, Block | Mibs)</name> - <name>log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_txt(LogDir)</name> + <name since="">log_to_txt(LogDir, Block | Mibs)</name> + <name since="">log_to_txt(LogDir, Mibs, Block | OutFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_txt(LogDir, Mibs, OutFile, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> <fsummary>Convert an Audit Trail Log to text format</fsummary> <type> <v>LogDir = string()</v> @@ -1257,15 +1257,15 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs) -> ok | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> - <name>log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Block | Mibs) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs) -> ok | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, Block | LogName) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, Block | LogFile) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block | Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R15B01">log_to_io(LogDir, Mibs, LogName, LogFile, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> + <name since="OTP R16B03">log_to_io(LogDir, Mibs, LogName, LogFile, Block, Start, Stop) -> ok | {ok, Cnt} | {error, Reason}</name> <fsummary>Convert an Audit Trail Log to text format</fsummary> <type> <v>LogDir = string()</v> @@ -1299,7 +1299,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>change_log_size(NewSize) -> ok | {error, Reason}</name> + <name since="">change_log_size(NewSize) -> ok | {error, Reason}</name> <fsummary>Change the size of the Audit Trail Log</fsummary> <type> <v>NewSize = {MaxBytes, MaxFiles}</v> @@ -1321,7 +1321,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name> + <name since="">set_log_type(NewType) -> {ok, OldType} | {error, Reason}</name> <fsummary>Change the Audit Trail Log type</fsummary> <type> <v>NewType = OldType = atl_type()</v> @@ -1340,7 +1340,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>load_mib(Mib) -> ok | {error, Reason}</name> + <name since="">load_mib(Mib) -> ok | {error, Reason}</name> <fsummary>Load a MIB into the manager</fsummary> <type> <v>Mib = MibName</v> @@ -1361,7 +1361,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>unload_mib(Mib) -> ok | {error, Reason}</name> + <name since="">unload_mib(Mib) -> ok | {error, Reason}</name> <fsummary>Unload a MIB from the manager</fsummary> <type> <v>Mib = MibName</v> @@ -1382,7 +1382,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>which_mibs() -> Mibs</name> + <name since="">which_mibs() -> Mibs</name> <fsummary>Which mibs are loaded into the manager</fsummary> <type> <v>Mibs = [{MibName, MibFile}]</v> @@ -1397,7 +1397,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>name_to_oid(Name) -> {ok, Oids} | {error, Reason}</name> + <name since="">name_to_oid(Name) -> {ok, Oids} | {error, Reason}</name> <fsummary>Get all the possible oid's for an alias-name</fsummary> <type> <v>Name = atom()</v> @@ -1414,7 +1414,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>oid_to_name(Oid) -> {ok, Name} | {error, Reason}</name> + <name since="">oid_to_name(Oid) -> {ok, Name} | {error, Reason}</name> <fsummary>Get the alias-name of the oid </fsummary> <type> <v>Oid = oid()</v> @@ -1429,7 +1429,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>oid_to_type(Oid) -> {ok, Type} | {error, Reason}</name> + <name since="">oid_to_type(Oid) -> {ok, Type} | {error, Reason}</name> <fsummary>Get the type of the the oid</fsummary> <type> <v>Oid = oid()</v> @@ -1444,7 +1444,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>backup(BackupDir) -> ok | {error, Reason}</name> + <name since="">backup(BackupDir) -> ok | {error, Reason}</name> <fsummary>Backup manager data</fsummary> <type> <v>BackupDir = string()</v> @@ -1458,7 +1458,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>info() -> [{Key, Value}]</name> + <name since="">info() -> [{Key, Value}]</name> <fsummary>Return information about the manager</fsummary> <type> <v>Key = atom()</v> @@ -1475,7 +1475,7 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>verbosity(Ref, Verbosity) -> void()</name> + <name since="">verbosity(Ref, Verbosity) -> void()</name> <fsummary>Assign a new verbosity for the process</fsummary> <type> <v>Ref = server | config | net_if | note_store | all</v> @@ -1492,8 +1492,8 @@ priv_key = [integer()] (length is 16 if priv = usmDESPrivProtocol | usmAesCfb1 </func> <func> - <name>format_reason(Reason) -> string()</name> - <name>format_reason(Prefix, Reason) -> string()</name> + <name since="">format_reason(Reason) -> string()</name> + <name since="">format_reason(Prefix, Reason) -> string()</name> <fsummary>Assign a new verbosity for the process</fsummary> <type> <v>Reason = term()</v> diff --git a/lib/snmp/doc/src/snmpm_conf.xml b/lib/snmp/doc/src/snmpm_conf.xml index a3097e5f7e..17ecf2df7c 100644 --- a/lib/snmp/doc/src/snmpm_conf.xml +++ b/lib/snmp/doc/src/snmpm_conf.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpm_conf.xml</file> </header> - <module>snmpm_conf</module> + <module since="">snmpm_conf</module> <modulesummary>Utility functions for handling the manager config files.</modulesummary> <description> <p>The module <c>snmpm_conf</c> contains various utility functions to @@ -42,7 +42,7 @@ </description> <funcs> <func> - <name>manager_entry(Tag, Val) -> manager_entry()</name> + <name since="">manager_entry(Tag, Val) -> manager_entry()</name> <fsummary>Create an manager entry</fsummary> <type> <v>Tag = address | port | engine_id | max_message_size</v> @@ -60,8 +60,8 @@ </desc> </func> <func> - <name>write_manager_config(Dir, Conf) -> ok</name> - <name>write_manager_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_manager_config(Dir, Conf) -> ok</name> + <name since="">write_manager_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the manager config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -81,7 +81,7 @@ </desc> </func> <func> - <name>append_manager_config(Dir, Conf) -> ok</name> + <name since="">append_manager_config(Dir, Conf) -> ok</name> <fsummary>Append the manager config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -98,7 +98,7 @@ </desc> </func> <func> - <name>read_manager_config(Dir) -> Conf</name> + <name since="">read_manager_config(Dir) -> Conf</name> <fsummary>Read the manager config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -115,9 +115,9 @@ </desc> </func> <func> - <name>users_entry(UserId) -> users_entry()</name> - <name>users_entry(UserId, UserMod) -> users_entry()</name> - <name>users_entry(UserId, UserMod, UserData) -> users_entry()</name> + <name since="">users_entry(UserId) -> users_entry()</name> + <name since="">users_entry(UserId, UserMod) -> users_entry()</name> + <name since="">users_entry(UserId, UserMod, UserData) -> users_entry()</name> <fsummary>Create an users entry</fsummary> <type> <v>UserId = term()</v> @@ -139,8 +139,8 @@ </desc> </func> <func> - <name>write_users_config(Dir, Conf) -> ok</name> - <name>write_users_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_users_config(Dir, Conf) -> ok</name> + <name since="">write_users_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the manager users config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -161,7 +161,7 @@ </desc> </func> <func> - <name>append_users_config(Dir, Conf) -> ok</name> + <name since="">append_users_config(Dir, Conf) -> ok</name> <fsummary>Append the manager users config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -179,7 +179,7 @@ </desc> </func> <func> - <name>read_users_config(Dir) -> Conf</name> + <name since="">read_users_config(Dir) -> Conf</name> <fsummary>Read the manager users config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -196,7 +196,7 @@ </desc> </func> <func> - <name>agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name> + <name since="">agents_entry(UserId, TargetName, Comm, Domain, Addr, EngineID, Timeout, MaxMessageSize, Version, SecModel, SecName, SecLevel) -> agents_entry()</name> <fsummary>Create an agents entry</fsummary> <type> <v>UserId = term()</v> @@ -223,8 +223,8 @@ </desc> </func> <func> - <name>write_agents_config(Dir, Conf) -> ok</name> - <name>write_agents_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_agents_config(Dir, Conf) -> ok</name> + <name since="">write_agents_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the manager agents to the config file</fsummary> <type> <v>Dir = string()</v> @@ -245,7 +245,7 @@ </desc> </func> <func> - <name>append_agents_config(Dir, Conf) -> ok</name> + <name since="">append_agents_config(Dir, Conf) -> ok</name> <fsummary>Append the manager agents to the config file</fsummary> <type> <v>Dir = string()</v> @@ -263,7 +263,7 @@ </desc> </func> <func> - <name>read_agents_config(Dir) -> Conf</name> + <name since="">read_agents_config(Dir) -> Conf</name> <fsummary>Read the manager agents config from the config file</fsummary> <type> <v>Dir = string()</v> @@ -280,8 +280,8 @@ </desc> </func> <func> - <name>usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name> - <name>usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name> + <name since="">usm_entry(EngineID, UserName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name> + <name since="">usm_entry(EngineID, UserName, SecName, AuthP, AuthKey, PrivP, PrivKey) -> usm_entry()</name> <fsummary>Create an usm entry</fsummary> <type> <v>EngineID = string()</v> @@ -303,8 +303,8 @@ </desc> </func> <func> - <name>write_usm_config(Dir, Conf) -> ok</name> - <name>write_usm_config(Dir, Hdr, Conf) -> ok</name> + <name since="">write_usm_config(Dir, Conf) -> ok</name> + <name since="">write_usm_config(Dir, Hdr, Conf) -> ok</name> <fsummary>Write the manager usm config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -325,7 +325,7 @@ </desc> </func> <func> - <name>append_usm_config(Dir, Conf) -> ok</name> + <name since="">append_usm_config(Dir, Conf) -> ok</name> <fsummary>Append the manager usm config to the config file</fsummary> <type> <v>Dir = string()</v> @@ -343,7 +343,7 @@ </desc> </func> <func> - <name>read_usm_config(Dir) -> Conf</name> + <name since="">read_usm_config(Dir) -> Conf</name> <fsummary>Read the manager usm config from the config file</fsummary> <type> <v>Dir = string()</v> diff --git a/lib/snmp/doc/src/snmpm_mpd.xml b/lib/snmp/doc/src/snmpm_mpd.xml index 08276e4b30..b024f8d294 100644 --- a/lib/snmp/doc/src/snmpm_mpd.xml +++ b/lib/snmp/doc/src/snmpm_mpd.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpm_mpd.xml</file> </header> - <module>snmpm_mpd</module> + <module since="">snmpm_mpd</module> <modulesummary>Message Processing and Dispatch module for the SNMP manager</modulesummary> <description> <p>The module <c>snmpm_mpd</c> implements the version independent @@ -48,7 +48,7 @@ <funcs> <func> - <name>init_mpd(Vsns) -> mpd_state()</name> + <name since="">init_mpd(Vsns) -> mpd_state()</name> <fsummary>Initialize the MPD module</fsummary> <type> <v>Vsns = [Vsn]</v> @@ -64,7 +64,7 @@ </desc> </func> <func> - <name>process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name> + <name since="OTP 17.3">process_msg(Msg, Domain, Addr, State, NoteStore, Logger) -> {ok, Vsn, Pdu, PduMS, MsgData} | {discarded, Reason}</name> <fsummary>Process a message received from the network</fsummary> <type> <v>Msg = binary()</v> @@ -92,7 +92,7 @@ </desc> </func> <func> - <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name> + <name since="">generate_msg(Vsn, NoteStore, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name> <fsummary>Generate a request message to be sent to the network</fsummary> <type> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> @@ -117,7 +117,7 @@ </desc> </func> <func> - <name>generate_response_msg(Vsn, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name> + <name since="">generate_response_msg(Vsn, Pdu, MsgData, Logger) -> {ok, Packet} | {discarded, Reason}</name> <fsummary>Generate a response packet to be sent to the network</fsummary> <type> <v>Vsn = 'version-1' | 'version-2' | 'version-3'</v> diff --git a/lib/snmp/doc/src/snmpm_network_interface.xml b/lib/snmp/doc/src/snmpm_network_interface.xml index 73892aa2e8..e4a66ceef2 100644 --- a/lib/snmp/doc/src/snmpm_network_interface.xml +++ b/lib/snmp/doc/src/snmpm_network_interface.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpm_network_interface.xml</file> </header> - <module>snmpm_network_interface</module> + <module since="">snmpm_network_interface</module> <modulesummary>Behaviour module for the SNMP manager network interface.</modulesummary> <description> <p>This module defines the behaviour of the manager network @@ -79,7 +79,7 @@ <funcs> <func> - <name>start_link(Server, NoteStore) -> {ok, Pid} | {error, Reason}</name> + <name since="">start_link(Server, NoteStore) -> {ok, Pid} | {error, Reason}</name> <fsummary>Start-link the network interface process</fsummary> <type> <v>Server = pid()</v> @@ -95,7 +95,7 @@ </func> <func> - <name>stop(Pid) -> void()</name> + <name since="">stop(Pid) -> void()</name> <fsummary>Stop the network interface process</fsummary> <type> <v>Pid = pid()</v> @@ -108,7 +108,7 @@ </func> <func> - <name>send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name> + <name since="">send_pdu(Pid, Pdu, Vsn, MsgData, Domain, Addr, ExtraInfo) -> void()</name> <fsummary>Request the network interface process to send this pdu</fsummary> <type> <v>Pid = pid()</v> @@ -142,7 +142,7 @@ </func> <func> - <name>inform_response(Pid, Ref, Addr, Port) -> void()</name> + <name since="">inform_response(Pid, Ref, Addr, Port) -> void()</name> <fsummary>Send the inform-request ack</fsummary> <type> <v>Pid = pid()</v> @@ -163,7 +163,7 @@ </func> <func> - <name>note_store(Pid, NoteStore) -> void()</name> + <name since="">note_store(Pid, NoteStore) -> void()</name> <fsummary>Change the verbosity of the network interface process</fsummary> <type> <v>Pid = pid()</v> @@ -179,7 +179,7 @@ </func> <func> - <name>info(Pid) -> [{Key, Value}]</name> + <name since="">info(Pid) -> [{Key, Value}]</name> <fsummary>Return information about the running network interface process</fsummary> <type> <v>Pid = pid()</v> @@ -198,7 +198,7 @@ </func> <func> - <name>verbosity(Pid, Verbosity) -> void()</name> + <name since="">verbosity(Pid, Verbosity) -> void()</name> <fsummary>Change the verbosity of the network interface process</fsummary> <type> <v>Pid = pid()</v> @@ -212,7 +212,7 @@ </func> <func> - <name>get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name> + <name since="">get_log_type(Pid) -> {ok, LogType} | {error, Reason}</name> <fsummary>Get the Audit Trail Log type</fsummary> <type> <v>Pid = pid()</v> @@ -233,7 +233,7 @@ </func> <func> - <name>set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name> + <name since="">set_log_type(Pid, NewType) -> {ok, OldType} | {error, Reason}</name> <fsummary>Change the Audit Trail Log type</fsummary> <type> <v>Pid = pid()</v> diff --git a/lib/snmp/doc/src/snmpm_network_interface_filter.xml b/lib/snmp/doc/src/snmpm_network_interface_filter.xml index 742cd53fc6..a50572da51 100644 --- a/lib/snmp/doc/src/snmpm_network_interface_filter.xml +++ b/lib/snmp/doc/src/snmpm_network_interface_filter.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpm_network_interface_filter.xml</file> </header> - <module>snmpm_network_interface_filter</module> + <module since="">snmpm_network_interface_filter</module> <modulesummary>Behaviour module for the SNMP manager network-interface filter.</modulesummary> <description> <p>This module defines the behaviour of the manager network interface @@ -100,7 +100,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | <funcs> <func> - <name>accept_recv(Domain, Addr) -> boolean()</name> + <name since="">accept_recv(Domain, Addr) -> boolean()</name> <fsummary>Shall the received message be accepted</fsummary> <type> <v>Domain = transportDomain()</v> @@ -116,7 +116,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </func> <func> - <name>accept_send(Domain, Addr) -> boolean()</name> + <name since="">accept_send(Domain, Addr) -> boolean()</name> <fsummary>Shall the message be sent</fsummary> <type> <v>Domain = transportDomain()</v> @@ -132,7 +132,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </func> <func> - <name>accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name> + <name since="">accept_recv_pdu(Domain, Addr, PduType) -> boolean()</name> <fsummary>Shall the received pdu be accepted</fsummary> <type> <v>Domain = transportDomain()</v> @@ -150,7 +150,7 @@ pdu_type() = 'get-request' | 'get-next-request' | 'get-response' | </func> <func> - <name>accept_send_pdu(Domain, Addr, PduType) -> boolean()</name> + <name since="">accept_send_pdu(Domain, Addr, PduType) -> boolean()</name> <fsummary>Shall the pdu be sent</fsummary> <type> <v>Domain = transportDomain()</v> diff --git a/lib/snmp/doc/src/snmpm_user.xml b/lib/snmp/doc/src/snmpm_user.xml index 87ae1d224a..9abf596c83 100644 --- a/lib/snmp/doc/src/snmpm_user.xml +++ b/lib/snmp/doc/src/snmpm_user.xml @@ -32,7 +32,7 @@ <rev></rev> <file>snmpm_user.xml</file> </header> - <module>snmpm_user</module> + <module since="">snmpm_user</module> <modulesummary>Behaviour module for the SNMP manager user.</modulesummary> <description> <p>This module defines the behaviour of the manager user. @@ -93,7 +93,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), <funcs> <func> - <name>handle_error(ReqId, Reason, UserData) -> void()</name> + <name since="">handle_error(ReqId, Reason, UserData) -> void()</name> <fsummary>Handle error</fsummary> <type> <v>ReqId = integer()</v> @@ -122,7 +122,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name> + <name since="">handle_agent(Domain, Addr, Type, SnmpInfo, UserData) -> Reply</name> <fsummary>Handle agent</fsummary> <type> <v>Domain = transportDomainUdpIpv4 | transportDomainUdpIpv6</v> @@ -181,7 +181,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> void()</name> + <name since="">handle_pdu(TargetName, ReqId, SnmpPduInfo, UserData) -> void()</name> <fsummary>Handle the reply to an asynchronous request</fsummary> <type> <v>TargetName = target_name()</v> @@ -202,7 +202,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply</name> + <name since="">handle_trap(TargetName, SnmpTrapInfo, UserData) -> Reply</name> <fsummary>Handle a trap/notification message</fsummary> <type> <v>TargetName = TargetName2 = target_name()</v> @@ -225,7 +225,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_inform(TargetName, SnmpInformInfo, UserData) -> Reply</name> + <name since="">handle_inform(TargetName, SnmpInformInfo, UserData) -> Reply</name> <fsummary>Handle a inform message</fsummary> <type> <v>TargetName = TargetName2 = target_name()</v> @@ -253,7 +253,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_report(TargetName, SnmpReportInfo, UserData) -> Reply</name> + <name since="">handle_report(TargetName, SnmpReportInfo, UserData) -> Reply</name> <fsummary>Handle a report message</fsummary> <type> <v>TargetName = TargetName2 = target_name()</v> @@ -278,7 +278,7 @@ snmp_v1_trap_info() :: {Enteprise :: snmp:oid(), </func> <func> - <name>handle_invalid_result(IN, OUT) -> void()</name> + <name since="OTP R16B03">handle_invalid_result(IN, OUT) -> void()</name> <fsummary>Handle a report message</fsummary> <type> <v>IN = {Func, Args}</v> diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index 1012c80fb1..0bc4baf5eb 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,84 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.7.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed port leakage if a ssh:daemon call failed.</p> + <p> + Own Id: OTP-15397 Aux Id: ERL-801 </p> + </item> + </list> + </section> + +</section> + +<section><title>Ssh 4.7.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Incompatibility with newer OpenSSH fixed. Previously + versions 7.8 and later could cause Erlang SSH to exit.</p> + <p> + Own Id: OTP-15413</p> + </item> + <item> + <p> + The '<c>exec</c>' option for ssh daemons had wrong format + in the documentation.</p> + <p> + Own Id: OTP-15416</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Added public key methods ssh-ed25519 and ssh-ed448.</p> + <p> + Requires OpenSSL 1.1.1 or higher as cryptolib under the + OTP application <c>crypto</c>.</p> + <p> + Own Id: OTP-15094 Aux Id: OTP-15419 </p> + </item> + <item> + <p> + The SSH property tests are now adapted to the PropEr + testing tool.</p> + <p> + Own Id: OTP-15312</p> + </item> + <item> + <p> + The term "user" was not documented in the SSH app. A new + chapter with terminology is added to the User's Manual + where the term "user" is defined.</p> + <p> + A reference manual page about the module <c>ssh_file</c> + is also added. This is the default callback module for + user's keys, host keys etc.</p> + <p> + Own Id: OTP-15314</p> + </item> + <item> + <p> + Host and user key checking is made more robust.</p> + <p> + Own Id: OTP-15424</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.7.1</title> <section><title>Improvements and New Features</title> @@ -230,6 +308,21 @@ </section> </section> +<section><title>Ssh 4.6.9.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed port leakage if a ssh:daemon call failed.</p> + <p> + Own Id: OTP-15397 Aux Id: ERL-801 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.6.9.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 fcf79969d3..3fd6eae423 100644 --- a/lib/ssh/doc/src/ssh.xml +++ b/lib/ssh/doc/src/ssh.xml @@ -28,7 +28,7 @@ <date>2007-10-06</date> <rev></rev> </header> - <module>ssh</module> + <module since="">ssh</module> <modulesummary>Main API of the ssh application</modulesummary> <description> <p>This is the interface module for the <c>SSH</c> application. @@ -1059,17 +1059,17 @@ <!-- CLOSE/1 --> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Closes an SSH connection.</fsummary> <desc><p>Closes an SSH connection.</p></desc> </func> <!-- CONNECT/2 etc --> <func> - <name>connect(Host, Port, Options) -> Result </name> - <name>connect(Host, Port, Options, NegotiationTimeout) -> Result </name> - <name>connect(TcpSocket, Options) -> Result</name> - <name>connect(TcpSocket, Options, NegotiationTimeout) -> Result</name> + <name since="">connect(Host, Port, Options) -> Result </name> + <name since="">connect(Host, Port, Options, NegotiationTimeout) -> Result </name> + <name since="OTP 19.0">connect(TcpSocket, Options) -> Result</name> + <name since="">connect(TcpSocket, Options, NegotiationTimeout) -> Result</name> <fsummary>Connects to an SSH server.</fsummary> <type> <v>Host = <seealso marker="#type-host">host()</seealso></v> @@ -1098,7 +1098,7 @@ <!-- CONNECTION_INFO/1, CONNECTION_INFO/2 --> <func> - <name name="connection_info" arity="2"/> + <name name="connection_info" arity="2" since=""/> <fsummary>Retrieves information about a connection.</fsummary> <desc> <p>Retrieves information about a connection. The list <c>Keys</c> defines which information that @@ -1108,9 +1108,9 @@ <!-- DEAMON/1,2,3 --> <func> - <name>daemon(Port | TcpSocket) -> Result</name> - <name>daemon(Port | TcpSocket, Options) -> Result</name> - <name>daemon(HostAddress, Port, Options) -> Result</name> + <name since="">daemon(Port | TcpSocket) -> Result</name> + <name since="">daemon(Port | TcpSocket, Options) -> Result</name> + <name since="">daemon(HostAddress, Port, Options) -> Result</name> <fsummary>Starts a server listening for SSH connections.</fsummary> <type> <v>Port = integer()</v> @@ -1154,7 +1154,7 @@ <!-- DAEMON_INFO/1 --> <func> - <name name="daemon_info" arity="1"/> + <name name="daemon_info" arity="1" since="OTP 19.0"/> <fsummary>Get info about a daemon</fsummary> <desc> <p>Returns a key-value list with information about the daemon.</p> @@ -1164,7 +1164,7 @@ <!-- DEFAULT_ALGORITHMS/0 --> <func> - <name name="default_algorithms" arity="0"/> + <name name="default_algorithms" arity="0" since="OTP 18.0"/> <fsummary>Get a list declaring the supported algorithms</fsummary> <desc> <p>Returns a key-value list, where the keys are the different types of algorithms and the values are the @@ -1176,9 +1176,9 @@ <!-- SHELL/1,2,3 --> <func> - <name>shell(Host | TcpSocket) -> Result </name> - <name>shell(Host | TcpSocket, Options) -> Result </name> - <name>shell(Host, Port, Options) -> Result </name> + <name since="">shell(Host | TcpSocket) -> Result </name> + <name since="">shell(Host | TcpSocket, Options) -> Result </name> + <name since="">shell(Host, Port, Options) -> Result </name> <fsummary>Starts an interactive shell on a remote SSH server.</fsummary> <type> <v>Host = <seealso marker="#type-host">host()</seealso></v> @@ -1203,8 +1203,8 @@ </func> <func> - <name name="start" arity="0"/> - <name name="start" arity="1"/> + <name name="start" arity="0" since=""/> + <name name="start" arity="1" since=""/> <fsummary>Starts the SSH application.</fsummary> <desc> <p>Utility function that starts the applications <c>crypto</c>, <c>public_key</c>, @@ -1215,7 +1215,7 @@ </func> <func> - <name name="stop" arity="0"/> + <name name="stop" arity="0" since=""/> <fsummary>Stops the <c>ssh</c> application.</fsummary> <desc> <p>Stops the <c>ssh</c> application. @@ -1225,9 +1225,9 @@ </func> <func> - <name name="stop_daemon" arity="1"/> - <name name="stop_daemon" arity="2"/> - <name name="stop_daemon" arity="3"/> + <name name="stop_daemon" arity="1" since=""/> + <name name="stop_daemon" arity="2" since=""/> + <name name="stop_daemon" arity="3" since="OTP 21.0"/> <fsummary>Stops the listener and all connections started by the listener.</fsummary> <desc> <p>Stops the listener and all connections started by the listener.</p> @@ -1235,9 +1235,9 @@ </func> <func> - <name name="stop_listener" arity="1"/> - <name name="stop_listener" arity="2"/> - <name name="stop_listener" arity="3"/> + <name name="stop_listener" arity="1" since=""/> + <name name="stop_listener" arity="2" since=""/> + <name name="stop_listener" arity="3" since="OTP 21.0"/> <fsummary>Stops the listener, but leaves existing connections started by the listener operational.</fsummary> <desc> <p>Stops the listener, but leaves existing connections started by the listener operational.</p> diff --git a/lib/ssh/doc/src/ssh_client_channel.xml b/lib/ssh/doc/src/ssh_client_channel.xml index 9be4007c68..cd28b95fd3 100644 --- a/lib/ssh/doc/src/ssh_client_channel.xml +++ b/lib/ssh/doc/src/ssh_client_channel.xml @@ -29,7 +29,7 @@ <date></date> <rev></rev> </header> - <module>ssh_client_channel</module> + <module since="OTP 21.0">ssh_client_channel</module> <modulesummary>-behaviour(ssh_client_channel). (Replaces ssh_channel) </modulesummary> <description> @@ -68,8 +68,8 @@ <funcs> <func> - <name>call(ChannelRef, Msg) -></name> - <name>call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name> + <name since="OTP 21.0">call(ChannelRef, Msg) -></name> + <name since="OTP 21.0">call(ChannelRef, Msg, Timeout) -> Reply | {error, Reason}</name> <fsummary>Makes a synchronous call to a channel.</fsummary> <type> <v>ChannelRef = pid() </v> @@ -92,7 +92,7 @@ </func> <func> - <name>cast(ChannelRef, Msg) -> ok </name> + <name since="OTP 21.0">cast(ChannelRef, Msg) -> ok </name> <fsummary>Sends an asynchronous message to the channel ChannelRef and returns ok.</fsummary> <type> @@ -111,7 +111,7 @@ </func> <func> - <name>enter_loop(State) -> _ </name> + <name since="OTP 21.0">enter_loop(State) -> _ </name> <fsummary>Makes an existing process an ssh_client_channel (replaces ssh_channel) process.</fsummary> <type> <v>State = term()</v> @@ -131,7 +131,7 @@ </func> <func> - <name>init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name> + <name since="OTP 21.0">init(Options) -> {ok, State} | {ok, State, Timeout} | {stop, Reason} </name> <fsummary>Initiates an <c>ssh_client_channel</c> process.</fsummary> <type> <v>Options = [{Option, Value}]</v> @@ -173,7 +173,7 @@ </func> <func> - <name>reply(Client, Reply) -> _</name> + <name since="OTP 21.0">reply(Client, Reply) -> _</name> <fsummary>Sends a reply to a client.</fsummary> <type> <v>Client = opaque()</v> @@ -193,8 +193,8 @@ </func> <func> - <name>start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name> - <name>start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> + <name since="OTP 21.0">start(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> </name> + <name since="OTP 21.0">start_link(SshConnection, ChannelId, ChannelCb, CbInitArgs) -> {ok, ChannelRef} | {error, Reason}</name> <fsummary>Starts a process that handles an SSH channel.</fsummary> <type> @@ -244,7 +244,7 @@ <funcs> <func> - <name>Module:code_change(OldVsn, State, Extra) -> {ok, + <name since="OTP 21.0">Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name> <fsummary>Converts process state when code is changed.</fsummary> <type> @@ -287,7 +287,7 @@ </func> <func> - <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} | + <name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} | {stop, Reason}</name> <fsummary>Makes necessary initializations and returns the initial channel state if the initializations succeed.</fsummary> @@ -307,7 +307,7 @@ </func> <func> - <name>Module:handle_call(Msg, From, State) -> Result</name> + <name since="OTP 21.0">Module:handle_call(Msg, From, State) -> Result</name> <fsummary>Handles messages sent by calling <c>call/[2,3]</c>.</fsummary> <type> @@ -334,7 +334,7 @@ </func> <func> - <name>Module:handle_cast(Msg, State) -> Result</name> + <name since="OTP 21.0">Module:handle_cast(Msg, State) -> Result</name> <fsummary>Handles messages sent by calling <c>cast/2</c>.</fsummary> <type> @@ -355,7 +355,7 @@ </func> <func> - <name>Module:handle_msg(Msg, State) -> {ok, State} | + <name since="OTP 21.0">Module:handle_msg(Msg, State) -> {ok, State} | {stop, ChannelId, State}</name> <fsummary>Handles other messages than SSH connection protocol, @@ -389,7 +389,7 @@ </func> <func> - <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop, + <name since="OTP 21.0">Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop, ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> @@ -416,7 +416,7 @@ </func> <func> - <name>Module:terminate(Reason, State) -> _</name> + <name since="OTP 21.0">Module:terminate(Reason, State) -> _</name> <fsummary>Does cleaning up before channel process termination. </fsummary> <type> diff --git a/lib/ssh/doc/src/ssh_client_key_api.xml b/lib/ssh/doc/src/ssh_client_key_api.xml index bc77756147..9f2f3013e5 100644 --- a/lib/ssh/doc/src/ssh_client_key_api.xml +++ b/lib/ssh/doc/src/ssh_client_key_api.xml @@ -29,7 +29,7 @@ <date></date> <rev></rev> </header> - <module>ssh_client_key_api</module> + <module since="OTP R16B">ssh_client_key_api</module> <modulesummary> -behaviour(ssh_client_key_api). </modulesummary> @@ -86,7 +86,7 @@ <funcs> <func> - <name>Module:add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name> + <name since="OTP R16B">Module:add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name> <fsummary>Adds a host key to the set of trusted host keys.</fsummary> <type> <v>HostNames = string()</v> @@ -103,7 +103,7 @@ </func> <func> - <name>Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name> + <name since="OTP R16B">Module:is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name> <fsummary>Checks if a host key is trusted.</fsummary> <type> <v>Key = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v> @@ -125,7 +125,7 @@ </func> <func> - <name>Module:user_key(Algorithm, ConnectOptions) -> + <name since="OTP R16B">Module:user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name> <fsummary>Fetches the users <em>public key</em> matching the <c>Algorithm</c>.</fsummary> <type> diff --git a/lib/ssh/doc/src/ssh_connection.xml b/lib/ssh/doc/src/ssh_connection.xml index 8e1cf156a8..2a701929f6 100644 --- a/lib/ssh/doc/src/ssh_connection.xml +++ b/lib/ssh/doc/src/ssh_connection.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>ssh_connection</module> + <module since="">ssh_connection</module> <modulesummary> This module provides API functions to send SSH Connection Protocol events to the other side of an SSH channel. @@ -201,7 +201,7 @@ <funcs> <func> - <name>adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name> + <name since="">adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok</name> <fsummary>Adjusts the SSH flow control window.</fsummary> <type> <v>ConnectionRef = connection_ref()</v> @@ -221,7 +221,7 @@ </func> <func> - <name>close(ConnectionRef, ChannelId) -> ok</name> + <name since="">close(ConnectionRef, ChannelId) -> ok</name> <fsummary>Sends a close message on the channel <c>ChannelId</c>.</fsummary> <type> <v>ConnectionRef = connection_ref()</v> @@ -240,7 +240,7 @@ </func> <func> - <name>exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | + <name since="">exec(ConnectionRef, ChannelId, Command, TimeOut) -> ssh_request_status() | {error, reason()}</name> <fsummary>Requests that the server starts the execution of the given command.</fsummary> <type> @@ -284,7 +284,7 @@ </func> <func> - <name>exit_status(ConnectionRef, ChannelId, Status) -> ok</name> + <name since="">exit_status(ConnectionRef, ChannelId, Status) -> ok</name> <fsummary>Sends the exit status of a command to the client.</fsummary> <type> <v>ConnectionRef = connection_ref() </v> @@ -298,8 +298,8 @@ </func> <func> - <name>ptty_alloc(ConnectionRef, ChannelId, Options) -></name> - <name>ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | + <name since="OTP 17.5">ptty_alloc(ConnectionRef, ChannelId, Options) -></name> + <name since="OTP 17.4">ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> > ssh_request_status() | {error, reason()}</name> <fsummary>Sends an SSH Connection Protocol <c>pty_req</c>, to allocate a pseudo-terminal.</fsummary> @@ -339,7 +339,7 @@ </func> <func> - <name>reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> + <name since="">reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok</name> <fsummary>Sends status replies to requests that want such replies.</fsummary> <type> <v>ConnectionRef = connection_ref()</v> @@ -357,10 +357,10 @@ </func> <func> - <name>send(ConnectionRef, ChannelId, Data) -></name> - <name>send(ConnectionRef, ChannelId, Data, Timeout) -></name> - <name>send(ConnectionRef, ChannelId, Type, Data) -></name> - <name>send(ConnectionRef, ChannelId, Type, Data, TimeOut) -> + <name since="">send(ConnectionRef, ChannelId, Data) -></name> + <name since="">send(ConnectionRef, ChannelId, Data, Timeout) -></name> + <name since="">send(ConnectionRef, ChannelId, Type, Data) -></name> + <name since="">send(ConnectionRef, ChannelId, Type, Data, TimeOut) -> ok | {error, timeout} | {error, closed}</name> <fsummary>Sends channel data.</fsummary> <type> @@ -380,7 +380,7 @@ </func> <func> - <name>send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name> + <name since="">send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}</name> <fsummary>Sends EOF on channel <c>ChannelId</c>.</fsummary> <type> <v>ConnectionRef = connection_ref()</v> @@ -392,8 +392,8 @@ </func> <func> - <name>session_channel(ConnectionRef, Timeout) -></name> - <name>session_channel(ConnectionRef, InitialWindowSize, + <name since="">session_channel(ConnectionRef, Timeout) -></name> + <name since="">session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> {ok, channel_id()} | {error, reason()}</name> <fsummary>Opens a channel for an SSH session.</fsummary> <type> @@ -410,7 +410,7 @@ </func> <func> - <name>setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | + <name since="">setenv(ConnectionRef, ChannelId, Var, Value, TimeOut) -> ssh_request_status() | {error, reason()}</name> <fsummary>Environment variables can be passed to the shell/command to be started later.</fsummary> @@ -428,7 +428,7 @@ </func> <func> - <name>shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed} + <name since="">shell(ConnectionRef, ChannelId) -> ok | failure | {error, closed} </name> <fsummary>Requests that the user default shell (typically defined in /etc/passwd in Unix systems) is to be executed at the server end.</fsummary> @@ -448,7 +448,7 @@ </func> <func> - <name>subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | + <name since="">subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> ssh_request_status() | {error, reason()}</name> <fsummary>Requests to execute a predefined subsystem on the server.</fsummary> <type> diff --git a/lib/ssh/doc/src/ssh_file.xml b/lib/ssh/doc/src/ssh_file.xml index 6681d9c306..f1fef09083 100644 --- a/lib/ssh/doc/src/ssh_file.xml +++ b/lib/ssh/doc/src/ssh_file.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>ssh_file</module> + <module since="OTP 21.2">ssh_file</module> <modulesummary>Default callback module for the client's and server's database operations in the ssh application</modulesummary> <description> <p>This module is the default callback handler for the client's and the server's user and host "database" operations. @@ -169,7 +169,7 @@ <funcs> <func> - <name>host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name> + <name since="OTP 21.2">host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name> <fsummary></fsummary> <desc> <p><strong>Types and description</strong></p> @@ -195,7 +195,7 @@ </func> <func> - <name>is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name> + <name since="OTP 21.2">is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name> <fsummary></fsummary> <desc> <p><strong>Types and description</strong></p> @@ -216,7 +216,7 @@ </func> <func> - <name>add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name> + <name since="OTP 21.2">add_host_key(HostNames, PublicHostKey, ConnectOptions) -> ok | {error, Reason}</name> <fsummary></fsummary> <desc> <p><strong>Types and description</strong></p> @@ -235,7 +235,7 @@ </func> <func> - <name>is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name> + <name since="OTP 21.2">is_host_key(Key, Host, Algorithm, ConnectOptions) -> Result</name> <fsummary></fsummary> <desc> <p><strong>Types and description</strong></p> @@ -254,7 +254,7 @@ </func> <func> - <name>user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name> + <name since="OTP 21.2">user_key(Algorithm, ConnectOptions) -> {ok, PrivateKey} | {error, Reason}</name> <fsummary></fsummary> <desc> <p><strong>Types and description</strong></p> diff --git a/lib/ssh/doc/src/ssh_server_channel.xml b/lib/ssh/doc/src/ssh_server_channel.xml index 31ba9a3231..a4e18bbfbf 100644 --- a/lib/ssh/doc/src/ssh_server_channel.xml +++ b/lib/ssh/doc/src/ssh_server_channel.xml @@ -29,7 +29,7 @@ <date></date> <rev></rev> </header> - <module>ssh_server_channel</module> + <module since="OTP 21.0">ssh_server_channel</module> <modulesummary>-behaviour(ssh_server_channel). (Replaces ssh_daemon_channel) </modulesummary> <description> @@ -70,7 +70,7 @@ <funcs> <func> - <name>Module:init(Args) -> {ok, State} | {ok, State, timeout()} | + <name since="OTP 21.0">Module:init(Args) -> {ok, State} | {ok, State, timeout()} | {stop, Reason}</name> <fsummary>Makes necessary initializations and returns the initial channel state if the initializations succeed.</fsummary> @@ -93,7 +93,7 @@ </func> <func> - <name>Module:handle_msg(Msg, State) -> {ok, State} | + <name since="OTP 21.0">Module:handle_msg(Msg, State) -> {ok, State} | {stop, ChannelId, State}</name> <fsummary>Handles other messages than SSH connection protocol, @@ -125,7 +125,7 @@ </func> <func> - <name>Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop, + <name since="OTP 21.0">Module:handle_ssh_msg(Msg, State) -> {ok, State} | {stop, ChannelId, State}</name> <fsummary>Handles <c>ssh</c> connection protocol messages.</fsummary> <type> @@ -152,7 +152,7 @@ </func> <func> - <name>Module:terminate(Reason, State) -> _</name> + <name since="OTP 21.0">Module:terminate(Reason, State) -> _</name> <fsummary>Does cleaning up before channel process termination. </fsummary> <type> diff --git a/lib/ssh/doc/src/ssh_server_key_api.xml b/lib/ssh/doc/src/ssh_server_key_api.xml index e2a31bd5f5..013a788a4a 100644 --- a/lib/ssh/doc/src/ssh_server_key_api.xml +++ b/lib/ssh/doc/src/ssh_server_key_api.xml @@ -29,7 +29,7 @@ <date></date> <rev></rev> </header> - <module>ssh_server_key_api</module> + <module since="OTP R16B">ssh_server_key_api</module> <modulesummary> -behaviour(ssh_server_key_api). </modulesummary> @@ -87,7 +87,7 @@ <funcs> <func> - <name>Module:host_key(Algorithm, DaemonOptions) -> + <name since="OTP R16B">Module:host_key(Algorithm, DaemonOptions) -> {ok, Key} | {error, Reason}</name> <fsummary>Fetches the host’s private key.</fsummary> <type> @@ -111,7 +111,7 @@ </func> <func> - <name>Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name> + <name since="OTP R16B">Module:is_auth_key(PublicUserKey, User, DaemonOptions) -> Result</name> <fsummary>Checks if the user key is authorized.</fsummary> <type> <v>PublicUserKey = <seealso marker="public_key:public_key#type-public_key">public_key:public_key()</seealso></v> diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml index 8c105147d6..c89092798d 100644 --- a/lib/ssh/doc/src/ssh_sftp.xml +++ b/lib/ssh/doc/src/ssh_sftp.xml @@ -29,7 +29,7 @@ <rev></rev> <file>ssh_sftp.sgml</file> </header> - <module>ssh_sftp</module> + <module since="">ssh_sftp</module> <modulesummary>SFTP client.</modulesummary> <description> <p>This module implements an SSH FTP (SFTP) client. SFTP is a @@ -82,7 +82,7 @@ <funcs> <func> - <name>apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name> + <name since="">apread(ChannelPid, Handle, Position, Len) -> {async, N} | {error, reason()}</name> <fsummary>Reads asynchronously from an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -98,7 +98,7 @@ </func> <func> - <name>apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name> + <name since="">apwrite(ChannelPid, Handle, Position, Data) -> {async, N} | {error, reason()}</name> <fsummary>Writes asynchronously to an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -116,7 +116,7 @@ </func> <func> - <name>aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name> + <name since="">aread(ChannelPid, Handle, Len) -> {async, N} | {error, reason()}</name> <fsummary>Reads asynchronously from an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -137,7 +137,7 @@ </func> <func> - <name>awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name> + <name since="">awrite(ChannelPid, Handle, Data) -> {async, N} | {error, reason()}</name> <fsummary>Writes asynchronously to an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -159,8 +159,8 @@ </func> <func> - <name>close(ChannelPid, Handle) -></name> - <name>close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name> + <name since="">close(ChannelPid, Handle) -></name> + <name since="">close(ChannelPid, Handle, Timeout) -> ok | {error, reason()}</name> <fsummary>Closes an open handle.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -173,8 +173,8 @@ </func> <func> - <name>delete(ChannelPid, Name) -></name> - <name>delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name since="">delete(ChannelPid, Name) -></name> + <name since="">delete(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> <fsummary>Deletes a file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -188,8 +188,8 @@ </func> <func> - <name>del_dir(ChannelPid, Name) -></name> - <name>del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name since="">del_dir(ChannelPid, Name) -></name> + <name since="">del_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> <fsummary>Deletes an empty directory.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -204,8 +204,8 @@ </func> <func> - <name>list_dir(ChannelPid, Path) -></name> - <name>list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name> + <name since="">list_dir(ChannelPid, Path) -></name> + <name since="">list_dir(ChannelPid, Path, Timeout) -> {ok, Filenames} | {error, reason()}</name> <fsummary>Lists the directory.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -221,8 +221,8 @@ </func> <func> - <name>make_dir(ChannelPid, Name) -></name> - <name>make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> + <name since="">make_dir(ChannelPid, Name) -></name> + <name since="">make_dir(ChannelPid, Name, Timeout) -> ok | {error, reason()}</name> <fsummary>Creates a directory.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -237,8 +237,8 @@ </func> <func> - <name>make_symlink(ChannelPid, Name, Target) -></name> - <name>make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name> + <name since="">make_symlink(ChannelPid, Name, Target) -></name> + <name since="">make_symlink(ChannelPid, Name, Target, Timeout) -> ok | {error, reason()}</name> <fsummary>Creates a symbolic link.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -253,8 +253,8 @@ </func> <func> - <name>open(ChannelPid, File, Mode) -></name> - <name>open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name since="">open(ChannelPid, File, Mode) -></name> + <name since="">open(ChannelPid, File, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> <fsummary>Opens a file and returns a handle.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -270,8 +270,8 @@ </desc> </func> <func> - <name>opendir(ChannelPid, Path) -></name> - <name>opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name since="">opendir(ChannelPid, Path) -></name> + <name since="">opendir(ChannelPid, Path, Timeout) -> {ok, Handle} | {error, reason()}</name> <fsummary>Opens a directory and returns a handle.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -285,8 +285,8 @@ </func> <func> - <name>open_tar(ChannelPid, Path, Mode) -></name> - <name>open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> + <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode) -></name> + <name since="OTP 17.4">open_tar(ChannelPid, Path, Mode, Timeout) -> {ok, Handle} | {error, reason()}</name> <fsummary>Opens a tar file on the server to which <c>ChannelPid</c> is connected and returns a handle.</fsummary> <type> @@ -339,8 +339,8 @@ </func> <func> - <name>position(ChannelPid, Handle, Location) -></name> - <name>position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name> + <name since="">position(ChannelPid, Handle, Location) -></name> + <name since="">position(ChannelPid, Handle, Location, Timeout) -> {ok, NewPosition | {error, reason()}</name> <fsummary>Sets the file position of a file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -384,8 +384,8 @@ </func> <func> - <name>pread(ChannelPid, Handle, Position, Len) -></name> - <name>pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name since="">pread(ChannelPid, Handle, Position, Len) -></name> + <name since="">pread(ChannelPid, Handle, Position, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> <fsummary>Reads from an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -402,8 +402,8 @@ </func> <func> - <name>pwrite(ChannelPid, Handle, Position, Data) -> ok</name> - <name>pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name> + <name since="">pwrite(ChannelPid, Handle, Position, Data) -> ok</name> + <name since="">pwrite(ChannelPid, Handle, Position, Data, Timeout) -> ok | {error, reason()}</name> <fsummary>Writes to an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -419,8 +419,8 @@ </func> <func> - <name>read(ChannelPid, Handle, Len) -></name> - <name>read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> + <name since="">read(ChannelPid, Handle, Len) -></name> + <name since="">read(ChannelPid, Handle, Len, Timeout) -> {ok, Data} | eof | {error, reason()}</name> <fsummary>Reads from an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -441,8 +441,8 @@ </func> <func> - <name>read_file(ChannelPid, File) -></name> - <name>read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name> + <name since="">read_file(ChannelPid, File) -></name> + <name since="">read_file(ChannelPid, File, Timeout) -> {ok, Data} | {error, reason()}</name> <fsummary>Reads a file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -456,8 +456,8 @@ </func> <func> - <name>read_file_info(ChannelPid, Name) -></name> - <name>read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <name since="">read_file_info(ChannelPid, Name) -></name> + <name since="">read_file_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> <fsummary>Gets information about a file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -481,8 +481,8 @@ </func> <func> - <name>read_link(ChannelPid, Name) -></name> - <name>read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name> + <name since="">read_link(ChannelPid, Name) -></name> + <name since="">read_link(ChannelPid, Name, Timeout) -> {ok, Target} | {error, reason()}</name> <fsummary>Reads symbolic link.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -496,8 +496,8 @@ </func> <func> - <name>read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name> - <name>read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> + <name since="">read_link_info(ChannelPid, Name) -> {ok, FileInfo} | {error, reason()}</name> + <name since="">read_link_info(ChannelPid, Name, Timeout) -> {ok, FileInfo} | {error, reason()}</name> <fsummary>Gets information about a symbolic link.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -517,8 +517,8 @@ </func> <func> - <name>rename(ChannelPid, OldName, NewName) -> </name> - <name>rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name> + <name since="">rename(ChannelPid, OldName, NewName) -> </name> + <name since="">rename(ChannelPid, OldName, NewName, Timeout) -> ok | {error, reason()}</name> <fsummary>Renames a file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -534,16 +534,16 @@ </func> <func> - <name>start_channel(ConnectionRef) -></name> - <name>start_channel(ConnectionRef, Options) -> + <name since="">start_channel(ConnectionRef) -></name> + <name since="">start_channel(ConnectionRef, Options) -> {ok, Pid} | {error, reason()|term()}</name> - <name>start_channel(Host, Options) -></name> - <name>start_channel(Host, Port, Options) -> + <name since="">start_channel(Host, Options) -></name> + <name since="">start_channel(Host, Port, Options) -> {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> - <name>start_channel(TcpSocket) -></name> - <name>start_channel(TcpSocket, Options) -> + <name since="">start_channel(TcpSocket) -></name> + <name since="">start_channel(TcpSocket, Options) -> {ok, Pid, ConnectionRef} | {error, reason()|term()}</name> <fsummary>Starts an SFTP client.</fsummary> @@ -594,7 +594,7 @@ </func> <func> - <name>stop_channel(ChannelPid) -> ok</name> + <name since="">stop_channel(ChannelPid) -> ok</name> <fsummary>Stops the SFTP client channel.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -606,8 +606,8 @@ </func> <func> - <name>write(ChannelPid, Handle, Data) -></name> - <name>write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name> + <name since="">write(ChannelPid, Handle, Data) -></name> + <name since="">write(ChannelPid, Handle, Data, Timeout) -> ok | {error, reason()}</name> <fsummary>Writes to an open file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -625,8 +625,8 @@ </func> <func> - <name>write_file(ChannelPid, File, Iolist) -></name> - <name>write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name> + <name since="">write_file(ChannelPid, File, Iolist) -></name> + <name since="">write_file(ChannelPid, File, Iolist, Timeout) -> ok | {error, reason()}</name> <fsummary>Writes a file.</fsummary> <type> <v>ChannelPid = pid()</v> @@ -641,8 +641,8 @@ </func> <func> - <name>write_file_info(ChannelPid, Name, Info) -></name> - <name>write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name> + <name since="">write_file_info(ChannelPid, Name, Info) -></name> + <name since="">write_file_info(ChannelPid, Name, Info, Timeout) -> ok | {error, reason()}</name> <fsummary>Writes information for a file.</fsummary> <type> <v>ChannelPid = pid()</v> diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml index 3b34150e98..ee72784add 100644 --- a/lib/ssh/doc/src/ssh_sftpd.xml +++ b/lib/ssh/doc/src/ssh_sftpd.xml @@ -29,7 +29,7 @@ <rev></rev> <file>ssh_sftpd.sgml</file> </header> - <module>ssh_sftpd</module> + <module since="">ssh_sftpd</module> <modulesummary>Specifies the channel process to handle an SFTP subsystem.</modulesummary> <description> <p>Specifies a channel process to handle an SFTP subsystem.</p> @@ -51,7 +51,7 @@ </section> <funcs> <func> - <name>subsystem_spec(Options) -> subsystem_spec()</name> + <name since="">subsystem_spec(Options) -> subsystem_spec()</name> <fsummary>Returns the subsystem specification that allows an SSH daemon to handle the subsystem "sftp".</fsummary> <type> <v>Options = [{Option, Value}]</v> diff --git a/lib/ssh/src/ssh.erl b/lib/ssh/src/ssh.erl index 086fa6e5f8..9281bf84a7 100644 --- a/lib/ssh/src/ssh.erl +++ b/lib/ssh/src/ssh.erl @@ -270,25 +270,38 @@ daemon(Host0, Port0, UserOptions0) when 0 =< Port0, Port0 =< 65535, try {Host1, UserOptions} = handle_daemon_args(Host0, UserOptions0), #{} = Options0 = ssh_options:handle_options(server, UserOptions), - - {{Host,Port}, ListenSocket} = - open_listen_socket(Host1, Port0, Options0), - - %% Now Host,Port is what to use for the supervisor to register its name, - %% and ListenSocket is for listening on connections. But it is still owned - %% by self()... - - finalize_start(Host, Port, ?GET_OPT(profile, Options0), - ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options0), - fun(Opts, Result) -> - {_, Callback, _} = ?GET_OPT(transport, Opts), - receive - {request_control, ListenSocket, ReqPid} -> - ok = Callback:controlling_process(ListenSocket, ReqPid), - ReqPid ! {its_yours,ListenSocket}, - Result - end - end) + {open_listen_socket(Host1, Port0, Options0), Options0} + of + {{{Host,Port}, ListenSocket}, Options1} -> + try + %% Now Host,Port is what to use for the supervisor to register its name, + %% and ListenSocket is for listening on connections. But it is still owned + %% by self()... + finalize_start(Host, Port, ?GET_OPT(profile, Options1), + ?PUT_INTERNAL_OPT({lsocket,{ListenSocket,self()}}, Options1), + fun(Opts, Result) -> + {_, Callback, _} = ?GET_OPT(transport, Opts), + receive + {request_control, ListenSocket, ReqPid} -> + ok = Callback:controlling_process(ListenSocket, ReqPid), + ReqPid ! {its_yours,ListenSocket}, + Result + end + end) + of + {error,Err} -> + close_listen_socket(ListenSocket, Options1), + {error,Err}; + OK -> + OK + catch + error:Error -> + close_listen_socket(ListenSocket, Options1), + error(Error); + exit:Exit -> + close_listen_socket(ListenSocket, Options1), + exit(Exit) + end catch throw:bad_fd -> {error,bad_fd}; @@ -524,6 +537,15 @@ open_listen_socket(_Host0, Port0, Options0) -> {{LHost,LPort}, LSock}. %%%---------------------------------------------------------------- +close_listen_socket(ListenSocket, Options) -> + try + {_, Callback, _} = ?GET_OPT(transport, Options), + Callback:close(ListenSocket) + catch + _C:_E -> ok + end. + +%%%---------------------------------------------------------------- finalize_start(Host, Port, Profile, Options0, F) -> try %% throws error:Error if no usable hostkey is found diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl index 278f6a9780..aa9ba0f9bb 100644 --- a/lib/ssh/src/ssh_sftpd.erl +++ b/lib/ssh/src/ssh_sftpd.erl @@ -508,11 +508,8 @@ close_our_file({_,Fd}, FileMod, FS0) -> FS1. %%% stat: do the stat -stat(Vsn, ReqId, Data, State, F) when Vsn =< 3-> - <<?UINT32(BLen), BPath:BLen/binary>> = Data, - stat(ReqId, unicode:characters_to_list(BPath), State, F); -stat(Vsn, ReqId, Data, State, F) when Vsn >= 4-> - <<?UINT32(BLen), BPath:BLen/binary, ?UINT32(_Flags)>> = Data, +stat(Vsn, ReqId, Data, State, F) -> + <<?UINT32(BLen), BPath:BLen/binary, _/binary>> = Data, stat(ReqId, unicode:characters_to_list(BPath), State, F). fstat(Vsn, ReqId, Data, State) when Vsn =< 3-> diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index da94b5722f..5de6d52092 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -43,7 +43,9 @@ suite() -> {timetrap,{seconds,40}}]. all() -> - [{group, all_tests}]. + [{group, all_tests}, + daemon_already_started + ]. groups() -> [{all_tests, [parallel], [{group, ssh_renegotiate_SUITE}, @@ -801,6 +803,24 @@ daemon_already_started(Config) when is_list(Config) -> ssh:stop_daemon(Pid). %%-------------------------------------------------------------------- +%%% Test that a failed daemon start does not leave the port open +daemon_error_closes_port(Config) -> + GoodSystemDir = proplists:get_value(data_dir, Config), + Port = ssh_test_lib:inet_port(), + {error,_} = ssh_test_lib:daemon(Port, []), % No system dir + case ssh_test_lib:daemon(Port, [{system_dir, GoodSystemDir}]) of + {error,eaddrinuse} -> + {fail, "Port leakage"}; + {error,Error} -> + ct:log("Strange error: ~p",[Error]), + {fail, "Strange error"}; + {Pid, _Host, Port} -> + %% Ok + ssh:stop_daemon(Pid) + end. + + +%%-------------------------------------------------------------------- %%% check that known_hosts is updated correctly known_hosts(Config) when is_list(Config) -> SystemDir = proplists:get_value(data_dir, Config), diff --git a/lib/ssh/test/ssh_compat_SUITE.erl b/lib/ssh/test/ssh_compat_SUITE.erl index f4eef2dc77..8e82527c6e 100644 --- a/lib/ssh/test/ssh_compat_SUITE.erl +++ b/lib/ssh/test/ssh_compat_SUITE.erl @@ -1126,7 +1126,24 @@ prepare_local_directory(ServerRootDir) -> "chmod 222 unreadable_file", "exit"]. + check_local_directory(ServerRootDir) -> + TimesToTry = 3, % sleep 0.5, 1, 2 and then 4 secs (7.5s in total) + check_local_directory(ServerRootDir, 500, TimesToTry-1). + +check_local_directory(ServerRootDir, SleepTime, N) -> + case do_check_local_directory(ServerRootDir) of + {error,Error} when N>0 -> + %% Could be that the erlang side is faster and the docker's operations + %% are not yet finalized. + %% Sleep for a while and retry a few times: + timer:sleep(SleepTime), + check_local_directory(ServerRootDir, 2*SleepTime, N-1); + Other -> + Other + end. + +do_check_local_directory(ServerRootDir) -> case lists:sort(ok(file:list_dir(ServerRootDir)) -- [".",".."]) of ["ex_tst1","mydir","tst2"] -> {ok,Expect} = file:read_file(filename:join(ServerRootDir,"ex_tst1")), @@ -1161,6 +1178,7 @@ check_local_directory(ServerRootDir) -> {error,{bad_dir_contents,"/"}} end. + call_sftp_in_docker(Config, ServerIP, ServerPort, Cmnds, UserDir) -> {DockerIP,DockerPort} = ip_port(Config), {ok,C} = ssh:connect(DockerIP, DockerPort, diff --git a/lib/ssh/test/ssh_connection_SUITE.erl b/lib/ssh/test/ssh_connection_SUITE.erl index 778e4a5fc8..6aa587dc7f 100644 --- a/lib/ssh/test/ssh_connection_SUITE.erl +++ b/lib/ssh/test/ssh_connection_SUITE.erl @@ -1124,12 +1124,12 @@ start_our_shell(_User, _Peer) -> ssh_exec_echo(Cmd) -> spawn(fun() -> - io:format("echo "++Cmd ++ "\n") + io:format("echo ~s\n", [Cmd]) end). ssh_exec_echo(Cmd, User) -> spawn(fun() -> - io:format(io_lib:format("echo ~s ~s\n",[User,Cmd])) + io:format("echo ~s ~s\n",[User,Cmd]) end). ssh_exec_echo(Cmd, User, _PeerAddr) -> ssh_exec_echo(Cmd,User). diff --git a/lib/ssh/test/ssh_engine_SUITE.erl b/lib/ssh/test/ssh_engine_SUITE.erl index c2e6ac1fee..3adb23acdb 100644 --- a/lib/ssh/test/ssh_engine_SUITE.erl +++ b/lib/ssh/test/ssh_engine_SUITE.erl @@ -126,10 +126,17 @@ simple_connect(Config) -> load_engine() -> case crypto:get_test_engine() of {ok, Engine} -> - try crypto:engine_load(<<"dynamic">>, + try + %% The test engine has it's own fake rsa sign/verify that + %% you don't want to use, so exclude it from methods to load: + Methods = + crypto:engine_get_all_methods() -- [engine_method_rsa], + crypto:engine_load(<<"dynamic">>, [{<<"SO_PATH">>, Engine}, <<"LOAD">>], - []) + [], + Methods + ) catch error:notsup -> {error, notsup} diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index ae7b4cf3f2..2890d7fe5b 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,4 +1,4 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.7.1 +SSH_VSN = 4.7.3 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 4baedf7431..82eb8ff700 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -27,6 +27,135 @@ </header> <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 9.1.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix encoding of the SRP extension length field in ssl. + The old encoding of the SRP extension length could cause + interoperability problems with third party SSL + implementations when SRP was used.</p> + <p> + Own Id: OTP-15477 Aux Id: ERL-790 </p> + </item> + <item> + <p> + Guarantee active once data delivery, handling TCP stream + properly.</p> + <p> + Own Id: OTP-15504 Aux Id: ERL-371 </p> + </item> + <item> + <p> + Correct gen_statem returns for some error cases</p> + <p> + Own Id: OTP-15505</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed renegotiation bug. Client did not handle server + initiated renegotiation correctly after rewrite to two + connection processes, due to ERL-622 commit + d87ac1c55188f5ba5cdf72384125d94d42118c18. This could + manifest it self as a " bad_record_mac" alert.</p> + <p> + Also included are some optimizations</p> + <p> + Own Id: OTP-15489 Aux Id: ERL-308 </p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 9.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + PEM cache was not evicting expired entries due to due to + timezone confusion.</p> + <p> + Own Id: OTP-15368</p> + </item> + <item> + <p> + Make sure an error is returned if a "transport_accept + socket" is used in some other call than ssl:handshake* or + ssl:controlling_process</p> + <p> + Own Id: OTP-15384 Aux Id: ERL-756 </p> + </item> + <item> + <p> + Fix timestamp handling in the PEM-cache could cause + entries to not be invalidated at the correct time.</p> + <p> + Own Id: OTP-15402</p> + </item> + <item> + <p> + Extend check for undelivered data at closing, could under + some circumstances fail to deliver all data that was + actually received.</p> + <p> + Own Id: OTP-15412 Aux Id: ERL-731 </p> + </item> + <item> + <p> + Correct signature check for TLS-1.2 that allows different + algorithms for signature of peer cert and peer cert key. + Not all allowed combinations where accepted.</p> + <p> + Own Id: OTP-15415 Aux Id: ERL-763 </p> + </item> + <item> + <p> + Correct gen_statem return value, could cause + renegotiation to fail.</p> + <p> + Own Id: OTP-15418 Aux Id: ERL-770 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add engine support for RSA key exchange</p> + <p> + Own Id: OTP-15420 Aux Id: ERIERL-268 </p> + </item> + <item> + <p> + ssl now uses active n internally to boost performance. + Old active once behavior can be restored by setting + application variable see manual page for ssl application + (man 6).</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-15449</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 9.0.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index ef99ace351..200fb89a4d 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -28,7 +28,7 @@ <rev></rev> <file>ssl.xml</file> </header> - <module>ssl</module> + <module since="">ssl</module> <modulesummary>Interface Functions for Secure Socket Layer</modulesummary> <description> <p> @@ -83,8 +83,9 @@ <p><c>| {ciphers, ciphers()}</c></p> <p><c>| {user_lookup_fun, {fun(), term()}}, {psk_identity, string()}, {srp_identity, {string(), string()}}</c></p> - <p><c>| {reuse_sessions, boolean()}</c></p> - <p><c>| {reuse_session, fun()} {next_protocols_advertised, [binary()]}</c></p> + <p><c>| {reuse_sessions, boolean() | save()}</c></p> + <p><c>| {reuse_session, fun() | binary()} </c></p> + <p><c>| {next_protocols_advertised, [binary()]}</c></p> <p><c>| {client_preferred_next_protocols, {client | server, [binary()]} | {client | server, [binary()], binary()}}</c></p> <p><c>| {log_alert, boolean()}</c></p> @@ -593,11 +594,23 @@ fun(srp, Username :: string(), UserState :: term()) -> <item><p>In mode <c>verify_none</c> the default behavior is to allow all x509-path validation errors. See also option <c>verify_fun</c>.</p> </item> + + <tag><marker id="client_reuse_session"/><c>{reuse_session, binary()}</c></tag> + <item><p>Reuses a specific session earlier saved with the option + <c>{reuse_sessions, save} since ssl-9.2</c> + </p></item> - <tag><c>{reuse_sessions, boolean()}</c></tag> - <item><p>Specifies if the client is to try to reuse sessions - when possible.</p></item> - + <tag><c>{reuse_sessions, boolean() | save}</c></tag> + <item><p>When <c>save</c> is specified a new connection will be negotiated + and saved for later reuse. The session ID can be fetched with + <seealso marker="#connection_information">connection_information/2</seealso> + and used with the client option <seealso marker="#client_reuse_session">reuse_session</seealso> + The boolean value true specifies that if possible, automatized session reuse will + be performed. If a new session is created, and is unique in regard + to previous stored sessions, it will be saved for possible later reuse. + Value <c>save</c> since ssl-9.2 + </p></item> + <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag> <item><p>The DER-encoded trusted certificates. If this option is supplied it overrides option <c>cacertfile</c>.</p></item> @@ -796,11 +809,14 @@ fun(srp, Username :: string(), UserState :: term()) -> </item> <tag><c>{reuse_sessions, boolean()}</c></tag> - <item><p>Specifies if the server is to agree to reuse sessions - when requested by the clients. See also option <c>reuse_session</c>. + <item><p>The boolean value true specifies that the server will + agree to reuse sessions. Setting it to false will result in an empty + session table, that is no sessions will be reused. + See also option <seealso marker="#server_reuse_session">reuse_session</seealso> </p></item> - <tag><c>{reuse_session, fun(SuggestedSessionId, + <tag><marker id="server_reuse_session"/> + <c>{reuse_session, fun(SuggestedSessionId, PeerCert, Compression, CipherSuite) -> boolean()}</c></tag> <item><p>Enables the TLS/DTLS server to have a local policy for deciding if a session is to be reused or not. @@ -920,7 +936,7 @@ fun(srp, Username :: string(), UserState :: term()) -> <funcs> <func> - <name>append_cipher_suites(Deferred, Suites) -> ciphers() </name> + <name since="OTP 20.3">append_cipher_suites(Deferred, Suites) -> ciphers() </name> <fsummary></fsummary> <type> <v>Deferred = ciphers() | cipher_filters() </v> @@ -936,8 +952,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>cipher_suites() -></name> - <name>cipher_suites(Type) -> old_ciphers()</name> + <name since="OTP R14B">cipher_suites() -></name> + <name since="OTP R14B">cipher_suites(Type) -> old_ciphers()</name> <fsummary>Returns a list of supported cipher suites.</fsummary> <type> <v>Type = erlang | openssl | all</v> @@ -948,7 +964,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>cipher_suites(Supported, Version) -> ciphers()</name> + <name since="OTP 20.3">cipher_suites(Supported, Version) -> ciphers()</name> <fsummary>Returns a list of all default or all supported cipher suites.</fsummary> <type> @@ -962,8 +978,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>eccs() -></name> - <name>eccs(protocol_version()) -> [named_curve()]</name> + <name since="OTP 19.2">eccs() -></name> + <name since="OTP 19.2">eccs(protocol_version()) -> [named_curve()]</name> <fsummary>Returns a list of supported ECCs.</fsummary> <desc><p>Returns a list of supported ECCs. <c>eccs()</c> @@ -973,7 +989,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>clear_pem_cache() -> ok </name> + <name since="OTP 17.5">clear_pem_cache() -> ok </name> <fsummary> Clears the pem cache</fsummary> <desc><p>PEM files, used by ssl API-functions, are cached. The @@ -985,8 +1001,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connect(Socket, SslOptions) -> </name> - <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} + <name since="OTP R14B">connect(Socket, SslOptions) -> </name> + <name since="">connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name> <fsummary>Upgrades a <c>gen_tcp</c>, or equivalent, connected socket to an TLS socket.</fsummary> @@ -1022,8 +1038,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connect(Host, Port, Options) -></name> - <name>connect(Host, Port, Options, Timeout) -> + <name since="">connect(Host, Port, Options) -></name> + <name since="">connect(Host, Port, Options, Timeout) -> {ok, SslSocket}| {ok, SslSocket, Ext} | {error, Reason}</name> <fsummary>Opens an TLS/DTLS connection to <c>Host</c>, <c>Port</c>.</fsummary> <type> @@ -1065,7 +1081,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>close(SslSocket) -> ok | {error, Reason}</name> + <name since="">close(SslSocket) -> ok | {error, Reason}</name> <fsummary>Closes an TLS/DTLS connection.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1076,7 +1092,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name> + <name since="OTP 18.1">close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name> <fsummary>Closes an TLS connection.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1091,7 +1107,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>controlling_process(SslSocket, NewOwner) -> + <name since="">controlling_process(SslSocket, NewOwner) -> ok | {error, Reason}</name> <fsummary>Assigns a new controlling process to the TLS/DTLS socket.</fsummary> @@ -1107,7 +1123,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connection_information(SslSocket) -> + <name since="OTP 18.0">connection_information(SslSocket) -> {ok, Result} | {error, Reason} </name> <fsummary>Returns all the connection information. </fsummary> @@ -1128,7 +1144,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>connection_information(SslSocket, Items) -> + <name since="OTP 18.0">connection_information(SslSocket, Items) -> {ok, Result} | {error, Reason} </name> <fsummary>Returns the requested connection information. </fsummary> @@ -1150,7 +1166,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>filter_cipher_suites(Suites, Filters) -> ciphers()</name> + <name since="OTP 20.3">filter_cipher_suites(Suites, Filters) -> ciphers()</name> <fsummary></fsummary> <type> <v> Suites = ciphers()</v> @@ -1165,7 +1181,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>format_error(Reason) -> string()</name> + <name since="">format_error(Reason) -> string()</name> <fsummary>Returns an error string.</fsummary> <type> <v>Reason = term()</v> @@ -1176,7 +1192,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>getopts(SslSocket, OptionNames) -> + <name since="">getopts(SslSocket, OptionNames) -> {ok, [socketoption()]} | {error, Reason}</name> <fsummary>Gets the values of the specified options.</fsummary> <type> @@ -1190,9 +1206,9 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>getstat(SslSocket) -> + <name since="OTP 19.0">getstat(SslSocket) -> {ok, OptionValues} | {error, inet:posix()}</name> - <name>getstat(SslSocket, OptionNames) -> + <name since="OTP 19.0">getstat(SslSocket, OptionNames) -> {ok, OptionValues} | {error, inet:posix()}</name> <fsummary>Get one or more statistic options for a socket</fsummary> <type> @@ -1207,8 +1223,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>handshake(HsSocket) -> </name> - <name>handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name> + <name since="OTP 21.0">handshake(HsSocket) -> </name> + <name since="OTP 21.0">handshake(HsSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS handshake.</fsummary> <type> <v>HsSocket = SslSocket = sslsocket()</v> @@ -1222,8 +1238,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>handshake(Socket, SslOptions) -> </name> - <name>handshake(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name> + <name since="OTP 21.0">handshake(Socket, SslOptions) -> </name> + <name since="OTP 21.0">handshake(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {ok, SslSocket, Ext} | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary> <type> <v>Socket = socket() | sslsocket() </v> @@ -1261,7 +1277,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>handshake_cancel(SslSocket) -> ok </name> + <name since="OTP 21.0">handshake_cancel(SslSocket) -> ok </name> <fsummary>Cancel handshake with a fatal alert</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1272,8 +1288,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>handshake_continue(HsSocket, SSLOptions) -> {ok, SslSocket} | {error, Reason}</name> - <name>handshake_continue(HsSocket, SSLOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name> + <name since="OTP 21.0">handshake_continue(HsSocket, SSLOptions) -> {ok, SslSocket} | {error, Reason}</name> + <name since="OTP 21.0">handshake_continue(HsSocket, SSLOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary>Continue the SSL/TLS handshake.</fsummary> <type> <v>HsSocket = SslSocket = sslsocket()</v> @@ -1287,7 +1303,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>listen(Port, Options) -> + <name since="">listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}</name> <fsummary>Creates an SSL listen socket.</fsummary> <type> @@ -1301,7 +1317,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name> + <name since="OTP 18.0">negotiated_protocol(SslSocket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name> <fsummary>Returns the protocol negotiated through ALPN or NPN extensions.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1315,7 +1331,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name> + <name since="">peercert(SslSocket) -> {ok, Cert} | {error, Reason}</name> <fsummary>Returns the peer certificate.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1330,7 +1346,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>peername(SslSocket) -> {ok, {Address, Port}} | + <name since="">peername(SslSocket) -> {ok, {Address, Port}} | {error, Reason}</name> <fsummary>Returns the peer address and port.</fsummary> <type> @@ -1344,7 +1360,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>prepend_cipher_suites(Preferred, Suites) -> ciphers()</name> + <name since="OTP 20.3">prepend_cipher_suites(Preferred, Suites) -> ciphers()</name> <fsummary></fsummary> <type> <v>Preferred = ciphers() | cipher_filters() </v> @@ -1360,7 +1376,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name> + <name since="OTP R15B01">prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name> <fsummary>Uses a session Pseudo-Random Function to generate key material.</fsummary> <type> <v>Socket = sslsocket()</v> @@ -1380,8 +1396,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>recv(SslSocket, Length) -> </name> - <name>recv(SslSocket, Length, Timeout) -> {ok, Data} | {error, + <name since="">recv(SslSocket, Length) -> </name> + <name since="">recv(SslSocket, Length, Timeout) -> {ok, Data} | {error, Reason}</name> <fsummary>Receives data on a socket.</fsummary> <type> @@ -1407,7 +1423,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>renegotiate(SslSocket) -> ok | {error, Reason}</name> + <name since="OTP R14B">renegotiate(SslSocket) -> ok | {error, Reason}</name> <fsummary>Initiates a new handshake.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1420,7 +1436,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>send(SslSocket, Data) -> ok | {error, Reason}</name> + <name since="">send(SslSocket, Data) -> ok | {error, Reason}</name> <fsummary>Writes data to a socket.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1434,7 +1450,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>setopts(SslSocket, Options) -> ok | {error, Reason}</name> + <name since="">setopts(SslSocket, Options) -> ok | {error, Reason}</name> <fsummary>Sets socket options.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1447,7 +1463,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>set_log_level(Level) -> ok | {error, Reason}</name> + <name since="OTP 22.0">set_log_level(Level) -> ok | {error, Reason}</name> <fsummary>Sets log level for the SSL application.</fsummary> <type> <v>Level = atom()</v> @@ -1458,7 +1474,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>shutdown(SslSocket, How) -> ok | {error, Reason}</name> + <name since="OTP R14B">shutdown(SslSocket, How) -> ok | {error, Reason}</name> <fsummary>Immediately closes a socket.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1476,8 +1492,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>ssl_accept(SslSocket) -> </name> - <name>ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name> + <name since="">ssl_accept(SslSocket) -> </name> + <name since="">ssl_accept(SslSocket, Timeout) -> ok | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS handshake.</fsummary> <type> <v>SslSocket = sslsocket()</v> @@ -1491,8 +1507,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>ssl_accept(Socket, SslOptions) -> </name> - <name>ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name> + <name since="">ssl_accept(Socket, SslOptions) -> </name> + <name since="OTP R14B">ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name> <fsummary>Performs server-side SSL/TLS/DTLS handshake.</fsummary> <type> <v>Socket = socket() | sslsocket() </v> @@ -1507,7 +1523,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>sockname(SslSocket) -> {ok, {Address, Port}} | + <name since="">sockname(SslSocket) -> {ok, {Address, Port}} | {error, Reason}</name> <fsummary>Returns the local address and port.</fsummary> <type> @@ -1522,8 +1538,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>start() -> </name> - <name>start(Type) -> ok | {error, Reason}</name> + <name since="OTP R14B">start() -> </name> + <name since="OTP R14B">start(Type) -> ok | {error, Reason}</name> <fsummary>Starts the SSL application.</fsummary> <type> <v>Type = permanent | transient | temporary</v> @@ -1535,7 +1551,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>stop() -> ok </name> + <name since="OTP R14B">stop() -> ok </name> <fsummary>Stops the SSL application.</fsummary> <desc> <p>Stops the SSL application.</p> @@ -1543,7 +1559,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>suite_to_str(CipherSuite) -> String</name> + <name since="OTP 21.0">suite_to_str(CipherSuite) -> String</name> <fsummary>Returns the string representation of a cipher suite.</fsummary> <type> <v>CipherSuite = erl_cipher_suite()</v> @@ -1555,8 +1571,8 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>transport_accept(ListenSocket) -></name> - <name>transport_accept(ListenSocket, Timeout) -> + <name since="">transport_accept(ListenSocket) -></name> + <name since="">transport_accept(ListenSocket, Timeout) -> {ok, SslSocket} | {error, Reason}</name> <fsummary>Accepts an incoming connection and prepares for <c>ssl_accept</c>.</fsummary> @@ -1590,7 +1606,7 @@ fun(srp, Username :: string(), UserState :: term()) -> </func> <func> - <name>versions() -> [versions_info()]</name> + <name since="OTP R14B">versions() -> [versions_info()]</name> <fsummary>Returns version information relevant for the SSL application.</fsummary> <type> diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml index f6d9021d4a..893919aeb4 100644 --- a/lib/ssl/doc/src/ssl_app.xml +++ b/lib/ssl/doc/src/ssl_app.xml @@ -171,6 +171,20 @@ shutdown gracefully. Defaults to 5000 milliseconds. </p> </item> + + <tag><c><![CDATA[internal_active_n = integer() <optional>]]></c></tag> + <item> + <p> + For TLS connections this value is used to handle the + internal socket. As the implementation was changed from an + active once to an active N behavior (N = 100), for + performance reasons, this option exist for possible tweaking + or restoring of the old behavior (internal_active_n = 1) in + unforeseen scenarios. The option will not affect erlang + distribution over TLS that will always run in active N mode. + Added in ssl-9.1 (OTP-21.2). + </p> + </item> </taglist> </section> diff --git a/lib/ssl/doc/src/ssl_crl_cache.xml b/lib/ssl/doc/src/ssl_crl_cache.xml index 71c6d5e49e..b766cfd2d9 100644 --- a/lib/ssl/doc/src/ssl_crl_cache.xml +++ b/lib/ssl/doc/src/ssl_crl_cache.xml @@ -24,7 +24,7 @@ <file>ssl_crl_cache.xml</file> </header> - <module>ssl_crl_cache</module> + <module since="OTP 18.0">ssl_crl_cache</module> <modulesummary>CRL cache </modulesummary> <description> <p> @@ -37,7 +37,7 @@ <funcs> <func> - <name>delete(Entries) -> ok | {error, Reason} </name> + <name since="OTP 18.0">delete(Entries) -> ok | {error, Reason} </name> <fsummary> </fsummary> <type> <v> Entries = <seealso marker="stdlib:uri_string">uri_string:uri_string()</seealso> | {file, string()} | {der, [<seealso @@ -49,8 +49,8 @@ </desc> </func> <func> - <name>insert(CRLSrc) -> ok | {error, Reason}</name> - <name>insert(URI, CRLSrc) -> ok | {error, Reason}</name> + <name since="OTP 18.0">insert(CRLSrc) -> ok | {error, Reason}</name> + <name since="OTP 18.0">insert(URI, CRLSrc) -> ok | {error, Reason}</name> <fsummary> </fsummary> <type> <v> CRLSrc = {file, string()} | {der, [ <seealso diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml index c6774b4df6..c7e501867f 100644 --- a/lib/ssl/doc/src/ssl_crl_cache_api.xml +++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml @@ -24,7 +24,7 @@ <file>ssl_crl_cache_api.xml</file> </header> - <module>ssl_crl_cache_api</module> + <module since="OTP 18.0">ssl_crl_cache_api</module> <modulesummary>API for a SSL/TLS CRL (Certificate Revocation List) cache.</modulesummary> <description> <p> @@ -59,7 +59,7 @@ </section> <funcs> <func> - <name>fresh_crl(DistributionPoint, CRL) -> FreshCRL</name> + <name since="OTP 18.0">fresh_crl(DistributionPoint, CRL) -> FreshCRL</name> <fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to public_key:pkix_crls_validate/3 </fsummary> <type> @@ -76,8 +76,8 @@ </func> <func> - <name>lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name> - <name>lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name> + <name since="OTP 19.0">lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name> + <name since="OTP 18.0">lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name> <fsummary> </fsummary> <type> <v> DistributionPoint = dist_point() </v> @@ -106,7 +106,7 @@ </func> <func> - <name>select(Issuer, DbHandle) -> CRLs </name> + <name since="OTP 18.0">select(Issuer, DbHandle) -> CRLs </name> <fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary> <type> <v> Issuer = <seealso diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml index a84a3dfce9..463cf15309 100644 --- a/lib/ssl/doc/src/ssl_session_cache_api.xml +++ b/lib/ssl/doc/src/ssl_session_cache_api.xml @@ -28,7 +28,7 @@ <rev></rev> <file>ssl_session_cache_api.xml</file> </header> - <module>ssl_session_cache_api</module> + <module since="OTP R14B">ssl_session_cache_api</module> <modulesummary>TLS session cache API</modulesummary> <description> @@ -66,7 +66,7 @@ <funcs> <func> - <name>delete(Cache, Key) -> _</name> + <name since="OTP R14B">delete(Cache, Key) -> _</name> <fsummary>Deletes a cache entry.</fsummary> <type> <v>Cache = cache_ref()</v> @@ -80,7 +80,7 @@ </func> <func> - <name>foldl(Fun, Acc0, Cache) -> Acc</name> + <name since="OTP R14B">foldl(Fun, Acc0, Cache) -> Acc</name> <fsummary></fsummary> <type> <v></v> @@ -96,7 +96,7 @@ </func> <func> - <name>init(Args) -> opaque() </name> + <name since="OTP 18.0">init(Args) -> opaque() </name> <fsummary>Returns cache reference.</fsummary> <type> <v>Args = proplists:proplist()</v> @@ -121,7 +121,7 @@ </func> <func> - <name>lookup(Cache, Key) -> Entry</name> + <name since="OTP R14B">lookup(Cache, Key) -> Entry</name> <fsummary>Looks up a cache entry.</fsummary> <type> <v>Cache = cache_ref()</v> @@ -136,7 +136,7 @@ </func> <func> - <name>select_session(Cache, PartialKey) -> [session()]</name> + <name since="OTP R14B">select_session(Cache, PartialKey) -> [session()]</name> <fsummary>Selects sessions that can be reused.</fsummary> <type> <v>Cache = cache_ref()</v> @@ -151,7 +151,7 @@ </func> <func> - <name>size(Cache) -> integer()</name> + <name since="OTP 19.3">size(Cache) -> integer()</name> <fsummary>Returns the number of sessions in the cache.</fsummary> <type> <v>Cache = cache_ref()</v> @@ -166,7 +166,7 @@ </func> <func> - <name>terminate(Cache) -> _</name> + <name since="OTP R14B">terminate(Cache) -> _</name> <fsummary>Called by the process that handles the cache when it is about to terminate.</fsummary> <type> @@ -180,7 +180,7 @@ </func> <func> - <name>update(Cache, Key, Session) -> _</name> + <name since="OTP R14B">update(Cache, Key, Session) -> _</name> <fsummary>Caches a new session or updates an already cached one.</fsummary> <type> <v>Cache = cache_ref()</v> diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index 8ed4505256..cbd5c8e0a9 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -40,7 +40,7 @@ -export([start_fsm/8, start_link/7, init/1, pids/1]). %% State transition handling --export([next_record/1, next_event/3, next_event/4, handle_common_event/4]). +-export([next_event/3, next_event/4, handle_protocol_record/3]). %% Handshake handling -export([renegotiate/2, send_handshake/2, @@ -51,7 +51,7 @@ -export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]). %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1, +-export([encode_data/3, next_record/1, send/3, socket/5, setopts/3, getopts/3]). %% gen_statem state functions @@ -108,9 +108,11 @@ pids(_) -> %%==================================================================== %% State transition handling %%==================================================================== -next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> - {no_record, State#state{unprocessed_handshake_events = N-1}}; - +next_record(#state{handshake_env = + #handshake_env{unprocessed_handshake_events = N} = HsEnv} + = State) when N > 0 -> + {no_record, State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; next_record(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts = [#ssl_tls{epoch = Epoch} = CT | Rest]} = Buffers, @@ -142,14 +144,14 @@ next_record(#state{protocol_buffers = next_record(State#state{protocol_buffers = Buffers#protocol_buffers{dtls_cipher_texts = Rest}, connection_states = ConnectionStates}); -next_record(#state{role = server, - socket = {Listener, {Client, _}}} = State) -> +next_record(#state{static_env = #static_env{role = server, + socket = {Listener, {Client, _}}}} = State) -> dtls_packet_demux:active_once(Listener, Client, self()), {no_record, State}; -next_record(#state{role = client, - socket = {_Server, Socket} = DTLSSocket, - close_tag = CloseTag, - transport_cb = Transport} = State) -> +next_record(#state{static_env = #static_env{role = client, + socket = {_Server, Socket} = DTLSSocket, + close_tag = CloseTag, + transport_cb = Transport}} = State) -> case dtls_socket:setopts(Transport, Socket, [{active,once}]) of ok -> {no_record, State}; @@ -163,9 +165,9 @@ next_record(State) -> next_event(StateName, Record, State) -> next_event(StateName, Record, State, []). -next_event(connection = StateName, no_record, +next_event(StateName, no_record, #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> - case next_record_if_active(State0) of + case next_record(State0) of {no_record, State} -> ssl_connection:hibernate_after(StateName, State, Actions); {#ssl_tls{epoch = CurrentEpoch, @@ -179,21 +181,18 @@ next_event(connection = StateName, no_record, {#ssl_tls{epoch = Epoch, type = ?HANDSHAKE, version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - {NextRecord, State} = next_record(State2), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake {#ssl_tls{epoch = Epoch, type = ?CHANGE_CIPHER_SPEC, version = _Version}, State1} = _Record when Epoch == CurrentEpoch-1 -> - {State2, MoreActions} = send_handshake_flight(State1, CurrentEpoch), - {NextRecord, State} = next_record(State2), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State1, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); {#ssl_tls{epoch = _Epoch, - version = _Version}, State1} -> + version = _Version}, State} -> %% TODO maybe buffer later epoch - {Record, State} = next_record(State1), - next_event(StateName, Record, State, Actions); + next_event(StateName, no_record, State, Actions); {#alert{} = Alert, State} -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; @@ -211,24 +210,20 @@ next_event(connection = StateName, Record, #ssl_tls{epoch = Epoch, type = ?HANDSHAKE, version = _Version} when Epoch == CurrentEpoch-1 -> - {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - {NextRecord, State} = next_record(State1), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); %% From FLIGHT perspective CHANGE_CIPHER_SPEC is treated as a handshake #ssl_tls{epoch = Epoch, type = ?CHANGE_CIPHER_SPEC, version = _Version} when Epoch == CurrentEpoch-1 -> - {State1, MoreActions} = send_handshake_flight(State0, CurrentEpoch), - {NextRecord, State} = next_record(State1), - next_event(StateName, NextRecord, State, Actions ++ MoreActions); + {State, MoreActions} = send_handshake_flight(State0, CurrentEpoch), + next_event(StateName, no_record, State, Actions ++ MoreActions); _ -> next_event(StateName, no_record, State0, Actions) end; next_event(StateName, Record, #state{connection_states = #{current_read := #{epoch := CurrentEpoch}}} = State0, Actions) -> case Record of - no_record -> - {next_state, StateName, State0, Actions}; #ssl_tls{epoch = CurrentEpoch, version = Version} = Record -> State = dtls_version(StateName, Version, State0), @@ -237,43 +232,48 @@ next_event(StateName, Record, #ssl_tls{epoch = _Epoch, version = _Version} = _Record -> %% TODO maybe buffer later epoch - {Record, State} = next_record(State0), - next_event(StateName, Record, State, Actions); + next_event(StateName, no_record, State0, Actions); #alert{} = Alert -> {next_state, StateName, State0, [{next_event, internal, Alert} | Actions]} end. -handle_common_event(internal, #alert{} = Alert, StateName, - #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, StateName, State); +%%% DTLS record protocol level application data messages + +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName0, State0) -> + case ssl_connection:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State1} -> + {next_state, StateName, State, Actions} = next_event(StateName0, Record, State1), + ssl_connection:hibernate_after(StateName, State, Actions) + end; %%% DTLS record protocol level handshake messages -handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, #state{protocol_buffers = Buffers0, - negotiated_version = Version} = State0) -> + negotiated_version = Version} = State) -> try case dtls_handshake:get_dtls_handshake(Version, Data, Buffers0) of {[], Buffers} -> - {Record, State} = next_record(State0#state{protocol_buffers = Buffers}), - next_event(StateName, Record, State); + next_event(StateName, no_record, State#state{protocol_buffers = Buffers}); {Packets, Buffers} -> - State = State0#state{protocol_buffers = Buffers}, + HsEnv = State#state.handshake_env, Events = dtls_handshake_events(Packets), {next_state, StateName, - State#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + State#state{protocol_buffers = Buffers, + handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} end catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, StateName, State0) + handle_own_alert(Alert, Version, StateName, State) 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) -> +handle_protocol_record(#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, +handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, #state{negotiated_version = Version} = State) -> case decode_alerts(EncAlerts) of Alerts = [_|_] -> @@ -282,31 +282,30 @@ handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, Sta 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}. +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. %%==================================================================== %% Handshake handling %%==================================================================== -renegotiate(#state{role = client} = State, Actions) -> +renegotiate(#state{static_env = #static_env{role = client}} = State, Actions) -> %% Handle same way as if server requested %% the renegotiation {next_state, connection, State, [{next_event, internal, #hello_request{}} | Actions]}; -renegotiate(#state{role = server} = State0, Actions) -> +renegotiate(#state{static_env = #static_env{role = server}} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), State1 = prepare_flight(State0), - {State2, MoreActions} = send_handshake(HelloRequest, State1), - {Record, State} = next_record(State2), - next_event(hello, Record, State, Actions ++ MoreActions). + {State, MoreActions} = send_handshake(HelloRequest, State1), + next_event(hello, no_record, State, Actions ++ MoreActions). send_handshake(Handshake, #state{connection_states = ConnectionStates} = State) -> #{epoch := Epoch} = ssl_record:current_connection_state(ConnectionStates, write), send_handshake_flight(queue_handshake(Handshake, State), Epoch). -queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, +queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, negotiated_version = Version, flight_buffer = #{handshakes := HsBuffer0, change_cipher_spec := undefined, @@ -315,9 +314,9 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, Hist = update_handshake_history(Handshake0, Handshake, Hist0), State#state{flight_buffer = Flight0#{handshakes => [Handshake | HsBuffer0], next_sequence => Seq +1}, - tls_handshake_history = Hist}; + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; -queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, +queue_handshake(Handshake0, #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, negotiated_version = Version, flight_buffer = #{handshakes_after_change_cipher_spec := Buffer0, next_sequence := Seq} = Flight0} = State) -> @@ -325,7 +324,7 @@ queue_handshake(Handshake0, #state{tls_handshake_history = Hist0, Hist = update_handshake_history(Handshake0, Handshake, Hist0), State#state{flight_buffer = Flight0#{handshakes_after_change_cipher_spec => [Handshake | Buffer0], next_sequence => Seq +1}, - tls_handshake_history = Hist}. + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}. queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, connection_states = ConnectionStates0} = State) -> @@ -337,10 +336,11 @@ queue_change_cipher(ChangeCipher, #state{flight_buffer = Flight, reinit(State) -> %% To be API compatible with TLS NOOP here reinit_handshake_data(State). -reinit_handshake_data(#state{protocol_buffers = Buffers} = State) -> +reinit_handshake_data(#state{protocol_buffers = Buffers, + handshake_env = HsEnv} = State) -> State#state{premaster_secret = undefined, public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history(), + handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history()}, flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, flight_buffer = new_flight(), protocol_buffers = @@ -366,8 +366,8 @@ encode_alert(#alert{} = Alert, Version, ConnectionStates) -> dtls_record:encode_alert_record(Alert, Version, ConnectionStates). send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, + static_env = #static_env{socket = Socket, + transport_cb = Transport}, connection_states = ConnectionStates0} = State0) -> {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), @@ -394,23 +394,6 @@ protocol_name() -> encode_data(Data, Version, ConnectionStates0)-> dtls_record:encode_data(Data, Version, ConnectionStates0). -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> - case Buffer of - <<>> -> - {Record, State} = next_record(State0), - next_event(StateName, Record, State); - _ -> - {Record, State} = ssl_connection:read_application_data(<<>>, State0), - next_event(StateName, Record, State) - end. -next_record_if_active(State = - #state{socket_options = - #socket_options{active = false}}) -> - {no_record ,State}; - -next_record_if_active(State) -> - next_record(State). - send(Transport, {_, {{_,_}, _} = Socket}, Data) -> send(Transport, Socket, Data); send(Transport, Socket, Data) -> @@ -436,13 +419,15 @@ getopts(Transport, Socket, Tag) -> init(enter, _, State) -> {keep_state, State}; init({call, From}, {start, Timeout}, - #state{host = Host, port = Port, role = client, + #state{static_env = #static_env{host = Host, + port = Port, + role = client, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb + connection_states = ConnectionStates0 } = State0) -> Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, @@ -461,7 +446,8 @@ init({call, From}, {start, Timeout}, }, {Record, State} = next_record(State3), next_event(hello, Record, State, Actions); -init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) -> +init({call, _} = Type, Event, #state{static_env = #static_env{role = server, + data_tag = udp}} = State) -> Result = gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT}, protocol_specific = #{current_cookie_secret => dtls_v1:cookie_secret(), @@ -470,8 +456,7 @@ init({call, _} = Type, Event, #state{role = server, data_tag = udp} = State) -> max_ignored_alerts => 10}}), erlang:send_after(dtls_v1:cookie_timeout(), self(), new_cookie_secret), Result; - -init({call, _} = Type, Event, #state{role = server} = State) -> +init({call, _} = Type, Event, #state{static_env = #static_env{role = server}} = State) -> %% I.E. DTLS over sctp gen_handshake(?FUNCTION_NAME, Type, Event, State#state{flight_state = reliable}); init(Type, Event, State) -> @@ -486,8 +471,8 @@ error(enter, _, State) -> {keep_state, State}; error({call, From}, {start, _Timeout}, #state{protocol_specific = #{error := Error}} = State) -> - ssl_connection:stop_and_reply( - normal, {reply, From, {error, Error}}, State); + {stop_and_reply, {shutdown, normal}, + [{reply, From, {error, Error}}], State}; error({call, _} = Call, Msg, State) -> gen_handshake(?FUNCTION_NAME, Call, Msg, State); error(_, _, _) -> @@ -499,16 +484,17 @@ error(_, _, _) -> #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -hello(enter, _, #state{role = server} = State) -> +hello(enter, _, #state{static_env = #static_env{role = server}} = State) -> {keep_state, State}; -hello(enter, _, #state{role = client} = State0) -> +hello(enter, _, #state{static_env = #static_env{role = client}} = State0) -> {State, Actions} = handle_flight_timer(State0), {keep_state, State, Actions}; hello(internal, #client_hello{cookie = <<>>, client_version = Version} = Hello, - #state{role = server, - transport_cb = Transport, - socket = Socket, + #state{static_env = #static_env{role = server, + transport_cb = Transport, + socket = Socket}, + handshake_env = HsEnv, protocol_specific = #{current_cookie_secret := Secret}} = State0) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), Cookie = dtls_handshake:cookie(Secret, IP, Port, Hello), @@ -522,33 +508,40 @@ hello(internal, #client_hello{cookie = <<>>, State1 = prepare_flight(State0#state{negotiated_version = Version}), {State2, Actions} = send_handshake(VerifyRequest, State1), {Record, State} = next_record(State2), - next_event(?FUNCTION_NAME, Record, State#state{tls_handshake_history = ssl_handshake:init_handshake_history()}, Actions); -hello(internal, #hello_verify_request{cookie = Cookie}, #state{role = client, - host = Host, port = Port, + next_event(?FUNCTION_NAME, Record, + State#state{handshake_env = HsEnv#handshake_env{ + tls_handshake_history = + ssl_handshake:init_handshake_history()}}, + Actions); +hello(internal, #hello_verify_request{cookie = Cookie}, #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, ssl_options = SslOpts, session = #session{own_certificate = OwnCert} = Session0, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb + connection_states = ConnectionStates0 } = State0) -> Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, OwnCert), Version = Hello#client_hello.client_version, - State1 = prepare_flight(State0#state{tls_handshake_history = ssl_handshake:init_handshake_history()}), + State1 = prepare_flight(State0#state{handshake_env = + HsEnv#handshake_env{tls_handshake_history + = ssl_handshake:init_handshake_history()}}), {State2, Actions} = send_handshake(Hello, State1), - State3 = State2#state{negotiated_version = Version, %% Requested version - session = - Session0#session{session_id = - Hello#client_hello.session_id}}, - {Record, State} = next_record(State3), - next_event(?FUNCTION_NAME, Record, State, Actions); -hello(internal, #client_hello{extensions = Extensions} = Hello, #state{ssl_options = #ssl_options{handshake = hello}, - start_or_recv_from = From} = State) -> + State = State2#state{negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = + Hello#client_hello.session_id}}, + next_event(?FUNCTION_NAME, no_record, State, Actions); +hello(internal, #client_hello{extensions = Extensions} = Hello, + #state{ssl_options = #ssl_options{handshake = hello}, + start_or_recv_from = From} = State) -> {next_state, user_hello, State#state{start_or_recv_from = undefined, hello = Hello}, [{reply, From, {ok, Extensions}}]}; @@ -557,11 +550,12 @@ hello(internal, #server_hello{extensions = Extensions} = Hello, #state{ssl_optio {next_state, user_hello, State#state{start_or_recv_from = undefined, hello = Hello}, [{reply, From, {ok, Extensions}}]}; -hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server, - transport_cb = Transport, - socket = Socket, +hello(internal, #client_hello{cookie = Cookie} = Hello, #state{static_env = #static_env{role = server, + transport_cb = Transport, + socket = Socket}, protocol_specific = #{current_cookie_secret := Secret, - previous_cookie_secret := PSecret}} = State0) -> + previous_cookie_secret := PSecret} + } = State0) -> {ok, {IP, Port}} = dtls_socket:peername(Transport, Socket), case dtls_handshake:cookie(Secret, IP, Port, Hello) of Cookie -> @@ -576,11 +570,12 @@ hello(internal, #client_hello{cookie = Cookie} = Hello, #state{role = server, end end; hello(internal, #server_hello{} = Hello, - #state{connection_states = ConnectionStates0, - negotiated_version = ReqVersion, - role = client, - renegotiation = {Renegotiation, _}, - ssl_options = SslOptions} = State) -> + #state{ + static_env = #static_env{role = client}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, + connection_states = ConnectionStates0, + negotiated_version = ReqVersion, + ssl_options = SslOptions} = State) -> case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> handle_own_alert(Alert, ReqVersion, ?FUNCTION_NAME, State); @@ -596,8 +591,7 @@ hello(internal, {handshake, {#hello_verify_request{} = Handshake, _}}, State) -> {next_state, ?FUNCTION_NAME, State, [{next_event, internal, Handshake}]}; hello(internal, #change_cipher_spec{type = <<1>>}, State0) -> {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {Record, State2} = next_record(State1), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0), + {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}; hello(info, Event, State) -> @@ -648,8 +642,7 @@ certify(internal = Type, #server_hello_done{} = Event, State) -> ssl_connection:certify(Type, Event, prepare_flight(State), ?MODULE); certify(internal, #change_cipher_spec{type = <<1>>}, State0) -> {State1, Actions0} = send_handshake_flight(State0, retransmit_epoch(?FUNCTION_NAME, State0)), - {Record, State2} = next_record(State1), - {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, Record, State2, Actions0), + {next_state, ?FUNCTION_NAME, State, Actions} = next_event(?FUNCTION_NAME, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}; certify(state_timeout, Event, State) -> @@ -690,12 +683,17 @@ connection(enter, _, State) -> {keep_state, State}; connection(info, Event, State) -> gen_info(Event, ?FUNCTION_NAME, State); -connection(internal, #hello_request{}, #state{host = Host, port = Port, +connection(internal, #hello_request{}, #state{static_env = #static_env{host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb + }, + handshake_env = #handshake_env{ renegotiation = {Renegotiation, _}}, session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, + ssl_options = SslOpts, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> + connection_states = ConnectionStates0 + } = State0) -> Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), @@ -709,16 +707,19 @@ connection(internal, #hello_request{}, #state{host = Host, port = Port, session = Session0#session{session_id = Hello#client_hello.session_id}}), next_event(hello, Record, State, Actions); -connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> +connection(internal, #client_hello{} = Hello, #state{static_env = #static_env{role = server}, + allow_renegotiate = true} = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - {next_state, hello, State#state{allow_renegotiate = false, renegotiation = {true, peer}}, + {next_state, hello, State#state{allow_renegotiate = false, + handshake_env = #handshake_env{renegotiation = {true, peer}}}, [{next_event, internal, Hello}]}; -connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> +connection(internal, #client_hello{}, #state{static_env = #static_env{role = server}, + allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), State1 = send_alert(Alert, State0), {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), @@ -773,29 +774,35 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, _}, User, end, Monitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = ?MODULE, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb + }, + + #state{static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first} + }, + socket_options = SocketOptions, %% We do not want to save the password in the state so that %% could be written in the clear into error logs. ssl_options = SSLOptions#ssl_options{password = undefined}, session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, connection_states = ConnectionStates, protocol_buffers = #protocol_buffers{}, user_application = {Monitor, User}, user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, - renegotiation = {false, first}, allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, start_or_recv_from = undefined, - protocol_cb = ?MODULE, flight_buffer = new_flight(), flight_state = {retransmit, ?INITIAL_RETRANSMIT_TIMEOUT} }. @@ -836,17 +843,18 @@ decode_cipher_text(#state{protocol_buffers = #protocol_buffers{dtls_cipher_texts {Alert, State} end. -dtls_version(hello, Version, #state{role = server} = State) -> +dtls_version(hello, Version, #state{static_env = #static_env{role = server}} = State) -> State#state{negotiated_version = Version}; %%Inital version dtls_version(_,_, State) -> State. handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, #state{connection_states = ConnectionStates0, - port = Port, session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb, + static_env = #static_env{port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, + session = #session{own_certificate = Cert} = Session0, negotiated_protocol = CurrentProtocol, key_algorithm = KeyExAlg, ssl_options = SslOpts} = State0) -> @@ -865,7 +873,7 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, State = prepare_flight(State0#state{connection_states = ConnectionStates, negotiated_version = Version, hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, + handshake_env = HsEnv#handshake_env{client_hello_version = ClientVersion}, session = Session, negotiated_protocol = Protocol}), @@ -876,19 +884,19 @@ handle_client_hello(#client_hello{client_version = ClientVersion} = Hello, %% raw data from socket, unpack records handle_info({Protocol, _, _, _, Data}, StateName, - #state{data_tag = Protocol} = State0) -> + #state{static_env = #static_env{data_tag = Protocol}} = State0) -> case next_dtls_record(Data, StateName, State0) of {Record, State} -> next_event(StateName, Record, State); #alert{} = Alert -> ssl_connection:handle_normal_shutdown(Alert, StateName, State0), - ssl_connection:stop({shutdown, own_alert}, State0) + {stop, {shutdown, own_alert}, State0} end; handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, + #state{static_env = #static_env{socket = Socket, + close_tag = CloseTag}, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{dtls_cipher_texts = CTs}, - close_tag = CloseTag, negotiated_version = Version} = State) -> %% Note that as of DTLS 1.2 (TLS 1.1), %% failure to properly close a connection no longer requires that a @@ -907,7 +915,7 @@ handle_info({CloseTag, Socket}, StateName, ok end, ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - ssl_connection:stop({shutdown, transport_closed}, State); + {stop, {shutdown, transport_closed}, State}; true -> %% Fixes non-delivery of final DTLS record in {active, once}. %% Basically allows the application the opportunity to set {active, once} again @@ -928,8 +936,7 @@ handle_state_timeout(flight_retransmission_timeout, StateName, #state{flight_state = {retransmit, NextTimeout}} = State0) -> {State1, Actions0} = send_handshake_flight(State0#state{flight_state = {retransmit, NextTimeout}}, retransmit_epoch(StateName, State0)), - {Record, State2} = next_record(State1), - {next_state, StateName, State, Actions} = next_event(StateName, Record, State2, Actions0), + {next_state, StateName, State, Actions} = next_event(StateName, no_record, State1, Actions0), %% This will reset the retransmission timer by repeating the enter state event {repeat_state, State, Actions}. @@ -942,8 +949,8 @@ handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> handle_alerts(Alerts, ssl_connection:handle_alert(Alert, StateName, State)). -handle_own_alert(Alert, Version, StateName, #state{data_tag = udp, - role = Role, +handle_own_alert(Alert, Version, StateName, #state{static_env = #static_env{data_tag = udp, + role = Role}, ssl_options = Options} = State0) -> case ignore_alert(Alert, State0) of {true, State} -> @@ -1033,10 +1040,10 @@ next_flight(Flight) -> change_cipher_spec => undefined, handshakes_after_change_cipher_spec => []}. -handle_flight_timer(#state{data_tag = udp, +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, flight_state = {retransmit, Timeout}} = State) -> start_retransmision_timer(Timeout, State); -handle_flight_timer(#state{data_tag = udp, +handle_flight_timer(#state{static_env = #static_env{data_tag = udp}, flight_state = connection} = State) -> {State, []}; handle_flight_timer(State) -> @@ -1052,8 +1059,8 @@ new_timeout(N) when N =< 30 -> new_timeout(_) -> 60. -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := Flight, change_cipher_spec := undefined}, negotiated_version = Version, @@ -1064,8 +1071,8 @@ send_handshake_flight(#state{socket = Socket, send(Transport, Socket, Encoded), {State0#state{connection_states = ConnectionStates}, []}; -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := [_|_] = Flight0, change_cipher_spec := ChangeCipher, handshakes_after_change_cipher_spec := []}, @@ -1078,8 +1085,8 @@ send_handshake_flight(#state{socket = Socket, send(Transport, Socket, [HsBefore, EncChangeCipher]), {State0#state{connection_states = ConnectionStates}, []}; -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := [_|_] = Flight0, change_cipher_spec := ChangeCipher, handshakes_after_change_cipher_spec := Flight1}, @@ -1094,8 +1101,8 @@ send_handshake_flight(#state{socket = Socket, send(Transport, Socket, [HsBefore, EncChangeCipher, HsAfter]), {State0#state{connection_states = ConnectionStates}, []}; -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = #{handshakes := [], change_cipher_spec := ChangeCipher, handshakes_after_change_cipher_spec := Flight1}, @@ -1152,16 +1159,17 @@ log_ignore_alert(_, _, _, _) -> ok. send_application_data(Data, From, _StateName, - #state{socket = Socket, + #state{static_env = #static_env{socket = Socket, + protocol_cb = Connection, + transport_cb = Transport}, + handshake_env = HsEnv, negotiated_version = Version, - protocol_cb = Connection, - transport_cb = Transport, connection_states = ConnectionStates0, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State0) -> case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of true -> - renegotiate(State0#state{renegotiation = {true, internal}}, + renegotiate(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}}, [{next_event, {call, From}, {application_data, Data}}]); false -> {Msgs, ConnectionStates} = diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index 3dbda2c91b..eb0f742e70 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -214,8 +214,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, dtls_v1:corresponding_tls_version(Version), SslOpts, Session0, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} catch throw:Alert -> @@ -224,17 +222,16 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> - case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, - Compression, HelloExt, - dtls_v1:corresponding_tls_version(Version), - SslOpt, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; + try ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, + Compression, HelloExt, + dtls_v1:corresponding_tls_version(Version), + SslOpt, ConnectionStates0, Renegotiation) of {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + catch throw:Alert -> + Alert end. - %%-------------------------------------------------------------------- enc_handshake(#hello_verify_request{protocol_version = {Major, Minor}, @@ -343,8 +340,9 @@ decode_handshake(Version, ?CLIENT_HELLO, <<?UINT24(_), ?UINT16(_), ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> TLSVersion = dtls_v1:corresponding_tls_version(Version), + LegacyVersion = dtls_v1:corresponding_tls_version({Major, Minor}), Exts = ssl_handshake:decode_vector(Extensions), - DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, TLSVersion, client), + DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, TLSVersion, LegacyVersion, client), #client_hello{ client_version = {Major,Minor}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 2c3f8bc20f..616e9e26e7 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -942,8 +942,6 @@ handle_options(Opts0, Role, Host) -> {list, [{mode, list}]}], Opts0), assert_proplist(Opts), RecordCb = record_cb(Opts), - - ReuseSessionFun = fun(_, _, _, _) -> true end, CaCerts = handle_option(cacerts, Opts, undefined), {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} = @@ -1014,9 +1012,8 @@ handle_options(Opts0, Role, Host) -> Opts, undefined), %% Do not send by default tls_version(HighestVersion)), - %% Server side option - reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), - reuse_sessions = handle_option(reuse_sessions, Opts, true), + reuse_sessions = handle_reuse_sessions_option(reuse_sessions, Opts, Role), + reuse_session = handle_reuse_session_option(reuse_session, Opts, Role), secure_renegotiate = handle_option(secure_renegotiate, Opts, true), client_renegotiation = handle_option(client_renegotiation, Opts, default_option_role(server, true, Role), @@ -1211,11 +1208,16 @@ validate_option(srp_identity, {Username, Password}) {unicode:characters_to_binary(Username), unicode:characters_to_binary(Password)}; +validate_option(reuse_session, undefined) -> + undefined; validate_option(reuse_session, Value) when is_function(Value) -> Value; +validate_option(reuse_session, Value) when is_binary(Value) -> + Value; validate_option(reuse_sessions, Value) when is_boolean(Value) -> Value; - +validate_option(reuse_sessions, save = Value) -> + Value; validate_option(secure_renegotiate, Value) when is_boolean(Value) -> Value; validate_option(client_renegotiation, Value) when is_boolean(Value) -> @@ -1374,6 +1376,26 @@ handle_signature_algorithms_option(Value, Version) when is_list(Value) handle_signature_algorithms_option(_, _Version) -> undefined. +handle_reuse_sessions_option(Key, Opts, client) -> + Value = proplists:get_value(Key, Opts, true), + validate_option(Key, Value), + Value; +handle_reuse_sessions_option(Key, Opts0, server) -> + Opts = proplists:delete({Key, save}, Opts0), + Value = proplists:get_value(Key, Opts, true), + validate_option(Key, Value), + Value. + +handle_reuse_session_option(Key, Opts, client) -> + Value = proplists:get_value(Key, Opts, undefined), + validate_option(Key, Value), + Value; +handle_reuse_session_option(Key, Opts, server) -> + ReuseSessionFun = fun(_, _, _, _) -> true end, + Value = proplists:get_value(Key, Opts, ReuseSessionFun), + validate_option(Key, Value), + Value. + validate_options([]) -> []; validate_options([{Opt, Value} | Tail]) -> diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index c4b8e2172a..d08b2cc7ad 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -34,7 +34,7 @@ -include("tls_handshake_1_3.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/2, security_parameters/3, security_parameters_1_3/3, +-export([security_parameters/2, security_parameters/3, security_parameters_1_3/2, cipher_init/3, nonce_seed/2, decipher/6, cipher/5, aead_encrypt/5, aead_decrypt/6, suites/1, all_suites/1, crypto_support_filters/0, chacha_suites/1, anonymous_suites/1, psk_suites/1, psk_suites_anon/1, @@ -44,10 +44,11 @@ hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, random_bytes/1, calc_mac_hash/4, is_stream_ciphersuite/1, signature_scheme/1, - scheme_to_components/1, hash_size/1]). + scheme_to_components/1, hash_size/1, effective_key_bits/1, + key_material/1]). %% RFC 8446 TLS 1.3 --export([generate_client_shares/1, generate_server_share/1]). +-export([generate_client_shares/1, generate_server_share/1, add_zero_padding/2]). -compile(inline). @@ -88,23 +89,14 @@ security_parameters(Version, CipherSuite, SecParams) -> prf_algorithm = prf_algorithm(PrfHashAlg, Version), hash_size = hash_size(Hash)}. -security_parameters_1_3(SecParams, ClientRandom, CipherSuite) -> - #{cipher := Cipher, - mac := Hash, - prf := PrfHashAlg} = ssl_cipher_format:suite_definition(CipherSuite), +security_parameters_1_3(SecParams, CipherSuite) -> + #{cipher := Cipher, prf := PrfHashAlg} = + ssl_cipher_format:suite_definition(CipherSuite), SecParams#security_parameters{ - client_random = ClientRandom, cipher_suite = CipherSuite, bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher), - cipher_type = type(Cipher), - key_size = effective_key_bits(Cipher), - expanded_key_material_length = expanded_key_material(Cipher), - key_material_length = key_material(Cipher), - iv_size = iv_size(Cipher), - mac_algorithm = mac_algorithm(Hash), - prf_algorithm =prf_algorithm(PrfHashAlg, {3,4}), - hash_size = hash_size(Hash), - compression_algorithm = 0}. + prf_algorithm = PrfHashAlg, %% HKDF hash algorithm + cipher_type = ?AEAD}. %%-------------------------------------------------------------------- -spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}. @@ -578,7 +570,8 @@ crypto_support_filters() -> end]}. is_acceptable_keyexchange(KeyExchange, _Algos) when KeyExchange == psk; - KeyExchange == null -> + KeyExchange == null; + KeyExchange == any -> true; is_acceptable_keyexchange(KeyExchange, Algos) when KeyExchange == dh_anon; KeyExchange == dhe_psk -> @@ -621,7 +614,7 @@ is_acceptable_cipher(rc4_128, Algos) -> is_acceptable_cipher(des_cbc, Algos) -> proplists:get_bool(des_cbc, Algos); is_acceptable_cipher('3des_ede_cbc', Algos) -> - proplists:get_bool(des3_cbc, Algos); + proplists:get_bool(des_ede3, Algos); is_acceptable_cipher(aes_128_cbc, Algos) -> proplists:get_bool(aes_cbc128, Algos); is_acceptable_cipher(aes_256_cbc, Algos) -> @@ -690,10 +683,9 @@ hash_size(sha) -> hash_size(sha256) -> 32; hash_size(sha384) -> - 48. -%% Uncomment when adding cipher suite that needs it -%hash_size(sha512) -> -% 64. + 48; +hash_size(sha512) -> + 64. %%-------------------------------------------------------------------- %%% Internal functions @@ -897,8 +889,8 @@ scheme_to_components(ecdsa_secp521r1_sha512) -> {sha512, ecdsa, secp521r1}; scheme_to_components(rsa_pss_rsae_sha256) -> {sha256, rsa_pss_rsae, undefined}; scheme_to_components(rsa_pss_rsae_sha384) -> {sha384, rsa_pss_rsae, undefined}; scheme_to_components(rsa_pss_rsae_sha512) -> {sha512, rsa_pss_rsae, undefined}; -%% scheme_to_components(ed25519) -> {undefined, undefined, undefined}; -%% scheme_to_components(ed448) -> {undefined, undefined, undefined}; +scheme_to_components(ed25519) -> {undefined, undefined, undefined}; +scheme_to_components(ed448) -> {undefined, undefined, undefined}; scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined}; scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined}; scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined}; @@ -972,7 +964,7 @@ is_correct_padding(GenBlockCipher, {3, 1}, false) -> is_correct_padding(#generic_block_cipher{padding_length = Len, padding = Padding}, _, _) -> Len == byte_size(Padding) andalso - list_to_binary(lists:duplicate(Len, Len)) == Padding. + binary:copy(?byte(Len), Len) == Padding. get_padding(Length, BlockSize) -> get_padding_aux(BlockSize, Length rem BlockSize). @@ -981,7 +973,7 @@ get_padding_aux(_, 0) -> {0, <<>>}; get_padding_aux(BlockSize, PadLength) -> N = BlockSize - PadLength, - {N, list_to_binary(lists:duplicate(N, N))}. + {N, binary:copy(?byte(N), N)}. random_iv(IV) -> IVSz = byte_size(IV), @@ -1240,5 +1232,24 @@ generate_key_exchange(secp384r1) -> public_key:generate_key({namedCurve, secp384r1}); generate_key_exchange(secp521r1) -> public_key:generate_key({namedCurve, secp521r1}); +generate_key_exchange(x25519) -> + crypto:generate_key(ecdh, x25519); +generate_key_exchange(x448) -> + crypto:generate_key(ecdh, x448); generate_key_exchange(FFDHE) -> public_key:generate_key(ssl_dh_groups:dh_params(FFDHE)). + + +%% TODO: Move this functionality to crypto! +%% 7.4.1. Finite Field Diffie-Hellman +%% +%% For finite field groups, a conventional Diffie-Hellman [DH76] +%% computation is performed. The negotiated key (Z) is converted to a +%% byte string by encoding in big-endian form and left-padded with zeros +%% up to the size of the prime. This byte string is used as the shared +%% secret in the key schedule as specified above. +add_zero_padding(Bin, PrimeSize) + when byte_size (Bin) =:= PrimeSize -> + Bin; +add_zero_padding(Bin, PrimeSize) -> + add_zero_padding(<<0, Bin/binary>>, PrimeSize). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 2abc678ed9..af18ceb322 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -52,8 +52,8 @@ %% Alert and close handling -export([handle_own_alert/4, handle_alert/3, - handle_normal_shutdown/3, stop/2, stop_and_reply/3 - ]). + handle_normal_shutdown/3, + handle_trusted_certs_db/1]). %% Data handling -export([read_application_data/2, internal_renegotiation/2]). @@ -336,8 +336,8 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> %% Alert and close handling %%==================================================================== handle_own_alert(Alert, _, StateName, - #state{role = Role, - protocol_cb = Connection, + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, ssl_options = SslOpts} = State) -> try %% Try to tell the other side send_alert(Alert, StateName, State) @@ -352,175 +352,217 @@ handle_own_alert(Alert, _, StateName, catch _:_ -> ok end, - stop({shutdown, own_alert}, State). - -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}} = State) -> + {stop, {shutdown, own_alert}, State}. + +handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, + handshake_env = #handshake_env{renegotiation = {false, first}}, + start_or_recv_from = StartFrom} = State) -> Pids = Connection:pids(State), alert_user(Pids, 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} = State) -> +handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + protocol_cb = Connection, + tracker = Tracker}, + socket_options = Opts, + user_application = {_Mon, Pid}, + start_or_recv_from = RecvFrom} = State) -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection). 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} = State) -> + #state{static_env = #static_env{role = Role, + socket = Socket, + host = Host, + port = Port, + tracker = Tracker, + transport_cb = Transport, + protocol_cb = Connection}, + ssl_options = SslOpts, + start_or_recv_from = From, + session = Session, user_application = {_Mon, Pid}, + socket_options = Opts} = State) -> invalidate_session(Role, Host, Port, Session), log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, Connection), - stop(normal, State); + {stop, {shutdown, normal}, State}; handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, - StateName, State) -> + downgrade= StateName, State) -> + {next_state, StateName, State, [{next_event, internal, Alert}]}; +handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, + StateName, State) -> handle_normal_shutdown(Alert, StateName, State), - stop({shutdown, peer_close}, State); - + {stop,{shutdown, peer_close}, State}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{role = Role, ssl_options = SslOpts, protocol_cb = Connection, - renegotiation = {true, internal}} = State) -> + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, internal}}, + ssl_options = SslOpts} = State) -> log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), handle_normal_shutdown(Alert, StateName, State), - stop({shutdown, peer_close}, State); + {stop,{shutdown, peer_close}, State}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, connection = StateName, - #state{role = Role, - ssl_options = SslOpts, renegotiation = {true, From}, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = SslOpts + } = State0) -> log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), - State1 = Connection:reinit_handshake_data(State0), - {Record, State} = Connection:next_record(State1#state{renegotiation = undefined}), - Connection:next_event(connection, Record, State); + State = Connection:reinit_handshake_data(State0), + Connection:next_event(connection, no_record, State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}); handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{role = Role, - ssl_options = SslOpts, renegotiation = {true, From}, - protocol_cb = Connection} = State0) -> - log_alert(SslOpts#ssl_options.log_level, Role, - Connection:protocol_name(), StateName, - Alert#alert{role = opposite_role(Role)}), + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv, + ssl_options = SslOpts + } = State0) -> + log_alert(SslOpts#ssl_options.log_level, Role, + Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), gen_statem:reply(From, {error, renegotiation_rejected}), - {Record, State1} = Connection:next_record(State0), %% Go back to connection! - State = Connection:reinit(State1#state{renegotiation = undefined}), - Connection:next_event(connection, Record, State); + State = Connection:reinit(State0#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}), + Connection:next_event(connection, no_record, State); %% Gracefully log and ignore all other warning alerts handle_alert(#alert{level = ?WARNING} = Alert, StateName, - #state{ssl_options = SslOpts, protocol_cb = Connection, role = Role} = State0) -> + #state{static_env = #static_env{role = Role, + protocol_cb = Connection}, + ssl_options = SslOpts} = State) -> log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}), - {Record, State} = Connection:next_record(State0), - Connection:next_event(StateName, Record, State). + Connection:next_event(StateName, no_record, State). %%==================================================================== %% Data handling %%==================================================================== -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 +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName, Connection) -> + case Buffer of + <<>> -> + {Record, State} = Connection:next_record(State0), + Connection:next_event(StateName, Record, State); + _ -> + case read_application_data(<<>>, State0) of + {stop, _, _} = ShutdownError -> + ShutdownError; + {Record, State} -> + Connection:next_event(StateName, Record, State) + end + end. + +read_application_data( + Data, + #state{ + user_data_buffer = Buffer0, + erl_dist_handle = DHandle} = State) -> + %% + Buffer = bincat(Buffer0, Data), + case DHandle of + undefined -> + #state{ + socket_options = SocketOpts, + bytes_to_read = BytesToRead, + start_or_recv_from = RecvFrom, + timer = Timer} = State, + read_application_data( + Buffer, State, SocketOpts, RecvFrom, Timer, BytesToRead); + _ -> + try read_application_dist_data(Buffer, State, DHandle) + catch error:_ -> + {stop,disconnect, + State#state{ + user_data_buffer = Buffer, + bytes_to_read = undefined}} + end + end. + +read_application_dist_data(Buffer, State, DHandle) -> + case Buffer of + <<Size:32,Data:Size/binary>> -> + erlang:dist_ctrl_put_data(DHandle, Data), + {no_record, + State#state{ + user_data_buffer = <<>>, + bytes_to_read = undefined}}; + <<Size:32,Data:Size/binary,Rest/binary>> -> + erlang:dist_ctrl_put_data(DHandle, Data), + read_application_dist_data(Rest, State, DHandle); + _ -> + {no_record, + State#state{ + user_data_buffer = Buffer, + bytes_to_read = undefined}} + end. + +read_application_data( + Buffer0, State, SocketOpts0, RecvFrom, Timer, BytesToRead) -> + %% + case get_data(SocketOpts0, BytesToRead, Buffer0) of {ok, ClientData, Buffer} -> % Send data - #state{ssl_options = #ssl_options{erl_dist = Dist}, - erl_dist_data = DistData} = State0, - case Dist andalso is_dist_up(DistData) of - true -> - dist_app_data(ClientData, State0#state{user_data_buffer = Buffer, - bytes_to_read = undefined}); - _ -> - SocketOpt = - deliver_app_data(Connection:pids(State0), - 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 - end; + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + tracker = Tracker}, + user_application = {_Mon, Pid}} = State, + SocketOpts = + deliver_app_data( + Connection:pids(State), + Transport, Socket, SocketOpts0, + ClientData, Pid, RecvFrom, Tracker, Connection), + cancel_timer(Timer), + if + SocketOpts#socket_options.active =:= false; + Buffer =:= <<>> -> + %% Passive mode, wait for active once or recv + %% Active and empty, get more data + {no_record, + State#state{ + user_data_buffer = Buffer, + start_or_recv_from = undefined, + timer = undefined, + bytes_to_read = undefined, + socket_options = SocketOpts + }}; + true -> %% We have more data + read_application_data( + Buffer, State, SocketOpts, + undefined, undefined, undefined) + end; {more, Buffer} -> % no reply, we need more data - Connection:next_record(State0#state{user_data_buffer = Buffer}); + {no_record, State#state{user_data_buffer = Buffer}}; {passive, Buffer} -> - Connection:next_record_if_active(State0#state{user_data_buffer = Buffer}); + {no_record, State#state{user_data_buffer = Buffer}}; {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Connection:pids(State0), - Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker, Connection), - stop(normal, State0) - end. - -dist_app_data(ClientData, #state{protocol_cb = Connection, - erl_dist_data = #{dist_handle := undefined, - dist_buffer := DistBuff} = DistData} = State) -> - Connection:next_record_if_active(State#state{erl_dist_data = DistData#{dist_buffer => [ClientData, DistBuff]}}); -dist_app_data(ClientData, #state{erl_dist_data = #{dist_handle := DHandle, - dist_buffer := DistBuff} = ErlDistData, - protocol_cb = Connection, - user_data_buffer = Buffer, - socket_options = SOpts} = State) -> - Data = merge_dist_data(DistBuff, ClientData), - try erlang:dist_ctrl_put_data(DHandle, Data) of - _ when SOpts#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#state{erl_dist_data = ErlDistData#{dist_buffer => <<>>}}); - _ -> %% We have more data - read_application_data(<<>>, State) - catch error:_ -> - stop(State, disconnect) + #state{ + static_env = + #static_env{ + socket = Socket, + protocol_cb = Connection, + transport_cb = Transport, + tracker = Tracker}, + user_application = {_Mon, Pid}} = State, + deliver_packet_error( + Connection:pids(State), Transport, Socket, SocketOpts0, + Buffer0, Pid, RecvFrom, Tracker, Connection), + {stop, {shutdown, normal}, State} end. -merge_dist_data(<<>>, ClientData) -> - ClientData; -merge_dist_data(DistBuff, <<>>) -> - DistBuff; -merge_dist_data(DistBuff, ClientData) -> - [DistBuff, ClientData]. %%==================================================================== %% Help functions for tls|dtls_connection.erl %%==================================================================== @@ -569,7 +611,8 @@ handle_session(#server_hello{cipher_suite = CipherSuite, ssl_config(Opts, Role, State) -> ssl_config(Opts, Role, State, new). -ssl_config(Opts, Role, State0, Type) -> +ssl_config(Opts, Role, #state{static_env = InitStatEnv0, + handshake_env = HsEnv} = State0, Type) -> {ok, #{cert_db_ref := Ref, cert_db_handle := CertDbHandle, fileref_db_handle := FileRefHandle, @@ -581,20 +624,23 @@ ssl_config(Opts, Role, State0, Type) -> ssl_config:init(Opts, Role), TimeStamp = erlang:monotonic_time(), Session = State0#state.session, + State = State0#state{session = Session#session{own_certificate = OwnCert, time_stamp = TimeStamp}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - crl_db = CRLDbHandle, - session_cache = CacheHandle, + static_env = InitStatEnv0#static_env{ + 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 = Opts}, case Type of new -> - Handshake = ssl_handshake:init_handshake_history(), - State#state{tls_handshake_history = Handshake}; + Hist = ssl_handshake:init_handshake_history(), + State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}; continue -> State end. @@ -612,11 +658,10 @@ ssl_config(Opts, Role, State0, Type) -> init({call, From}, {start, Timeout}, State0, Connection) -> Timer = start_or_recv_cancel_timer(Timeout, From), - {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From, - timer = Timer}), - Connection:next_event(hello, Record, State); + Connection:next_event(hello, no_record, State0#state{start_or_recv_from = From, timer = Timer}); init({call, From}, {start, {Opts, EmOpts}, Timeout}, - #state{role = Role, ssl_options = OrigSSLOptions, + #state{static_env = #static_env{role = Role}, + ssl_options = OrigSSLOptions, socket_options = SockOpts} = State0, Connection) -> try SslOpts = ssl:handle_options(Opts, OrigSSLOptions), @@ -625,7 +670,7 @@ init({call, From}, {start, {Opts, EmOpts}, Timeout}, State#state{ssl_options = SslOpts, socket_options = new_emulated(EmOpts, SockOpts)}, Connection) catch throw:Error -> - stop_and_reply(normal, {reply, From, {error, Error}}, State0) + {stop_and_reply, {shutdown, normal}, {reply, From, {error, Error}}, State0} end; init({call, From}, {new_user, _} = Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); @@ -641,7 +686,7 @@ init(_Type, _Event, _State, _Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- error({call, From}, {close, _}, State, _Connection) -> - stop_and_reply(normal, {reply, From, ok}, State); + {stop_and_reply, {shutdown, normal}, {reply, From, ok}, State}; error({call, From}, _Msg, State, _Connection) -> {next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}. @@ -664,10 +709,11 @@ user_hello({call, From}, cancel, #state{negotiated_version = Version} = State, _ gen_statem:reply(From, ok), handle_own_alert(?ALERT_REC(?FATAL, ?USER_CANCELED, user_canceled), Version, ?FUNCTION_NAME, State); -user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, #state{hello = Hello, - role = Role, - start_or_recv_from = RecvFrom, - ssl_options = Options0} = State0, _Connection) -> +user_hello({call, From}, {handshake_continue, NewOptions, Timeout}, + #state{hello = Hello, + static_env = #static_env{role = Role}, + start_or_recv_from = RecvFrom, + ssl_options = Options0} = State0, _Connection) -> Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), Options = ssl:handle_options(NewOptions, Options0#ssl_options{handshake = full}), State = ssl_config(Options, Role, State0, continue), @@ -686,16 +732,16 @@ user_hello(_, _, _, _) -> abbreviated({call, From}, Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{role = server, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, negotiated_version = Version, expecting_finished = true, - tls_handshake_history = Handshake, session = #session{master_secret = MasterSecret}, connection_states = ConnectionStates0} = State0, Connection) -> case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, client, get_current_prf(ConnectionStates0, write), - MasterSecret, Handshake) of + MasterSecret, Hist) of verified -> ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), @@ -706,13 +752,14 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; abbreviated(internal, #finished{verify_data = Data} = Finished, - #state{role = client, tls_handshake_history = Handshake0, + #state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{tls_handshake_history = Hist0}, session = #session{master_secret = MasterSecret}, negotiated_version = Version, connection_states = ConnectionStates0} = State0, Connection) -> case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, server, get_pending_prf(ConnectionStates0, write), - MasterSecret, Handshake0) of + MasterSecret, Hist0) of verified -> ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), @@ -727,20 +774,20 @@ abbreviated(internal, #finished{verify_data = Data} = Finished, %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true} = State, Connection) -> - {Record, State} = - Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), - Connection:next_event(?FUNCTION_NAME, Record, - State#state{expecting_next_protocol_negotiation = false}); + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{negotiated_protocol = SelectedProtocol, + expecting_next_protocol_negotiation = false}); abbreviated(internal, #change_cipher_spec{type = <<1>>}, - #state{connection_states = ConnectionStates0} = State0, Connection) -> + #state{connection_states = ConnectionStates0} = State, Connection) -> ConnectionStates1 = ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - {Record, State} = Connection:next_record(State0#state{connection_states = - ConnectionStates1}), - Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); + Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = + ConnectionStates1, + expecting_finished = true}); abbreviated(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); abbreviated(Type, Msg, State, Connection) -> @@ -758,34 +805,34 @@ certify({call, From}, Msg, State, Connection) -> certify(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, - #state{role = server, negotiated_version = Version, + #state{static_env = #static_env{role = server}, + negotiated_version = Version, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = true}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); certify(internal, #certificate{asn1_certificates = []}, - #state{role = server, + #state{static_env = #static_env{role = server}, ssl_options = #ssl_options{verify = verify_peer, fail_if_no_peer_cert = false}} = State0, Connection) -> - {Record, State} = - Connection:next_record(State0#state{client_certificate_requested = false}), - Connection:next_event(?FUNCTION_NAME, Record, State); + Connection:next_event(?FUNCTION_NAME, no_record, State0#state{client_certificate_requested = false}); certify(internal, #certificate{}, - #state{role = server, + #state{static_env = #static_env{role = server}, negotiated_version = Version, ssl_options = #ssl_options{verify = verify_none}} = State, _) -> Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); certify(internal, #certificate{} = Cert, - #state{negotiated_version = Version, - role = Role, - host = Host, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - crl_db = CRLDbInfo, + #state{static_env = #static_env{ + role = Role, + host = Host, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbInfo}, + negotiated_version = Version, ssl_options = Opts} = State, Connection) -> case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts, CRLDbInfo, Role, Host) of @@ -796,7 +843,8 @@ certify(internal, #certificate{} = Cert, handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; certify(internal, #server_key_exchange{exchange_keys = Keys}, - #state{role = client, negotiated_version = Version, + #state{static_env = #static_env{role = client}, + negotiated_version = Version, key_algorithm = Alg, public_key_info = PubKeyInfo, session = Session, @@ -830,7 +878,8 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, end end; certify(internal, #certificate_request{}, - #state{role = client, negotiated_version = Version, + #state{static_env = #static_env{role = client}, + negotiated_version = Version, key_algorithm = Alg} = State, _) when Alg == dh_anon; Alg == ecdh_anon; Alg == psk; Alg == dhe_psk; Alg == ecdhe_psk; Alg == rsa_psk; @@ -838,36 +887,34 @@ certify(internal, #certificate_request{}, handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, ?FUNCTION_NAME, State); certify(internal, #certificate_request{}, - #state{session = #session{own_certificate = undefined}, - role = client} = State0, Connection) -> + #state{static_env = #static_env{role = client}, + session = #session{own_certificate = undefined}} = State, Connection) -> %% The 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. - {Record, State} = Connection:next_record(State0), - Connection:next_event(?FUNCTION_NAME, Record, State#state{client_certificate_requested = true}); + Connection:next_event(?FUNCTION_NAME, no_record, State#state{client_certificate_requested = true}); certify(internal, #certificate_request{} = CertRequest, - #state{session = #session{own_certificate = Cert}, - role = client, - ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, - negotiated_version = Version} = State0, Connection) -> - case ssl_handshake:select_hashsign(CertRequest, Cert, - SupportedHashSigns, - ssl:tls_version(Version)) of + #state{static_env = #static_env{role = client}, + session = #session{own_certificate = Cert}, + ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, + negotiated_version = Version} = State, Connection) -> + case ssl_handshake:select_hashsign(CertRequest, Cert, + SupportedHashSigns, ssl:tls_version(Version)) of #alert {} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0); - NegotiatedHashSign -> - {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), - Connection:next_event(?FUNCTION_NAME, Record, - State#state{cert_hashsign_algorithm = NegotiatedHashSign}) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State); + NegotiatedHashSign -> + Connection:next_event(?FUNCTION_NAME, no_record, + State#state{client_certificate_requested = true, + cert_hashsign_algorithm = NegotiatedHashSign}) end; %% PSK and RSA_PSK might bypass the Server-Key-Exchange certify(internal, #server_hello_done{}, - #state{session = #session{master_secret = undefined}, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = undefined}, negotiated_version = Version, psk_identity = PSKIdentity, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, premaster_secret = undefined, - role = client, key_algorithm = Alg} = State0, Connection) when Alg == psk -> case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of @@ -879,12 +926,12 @@ certify(internal, #server_hello_done{}, client_certify_and_key_exchange(State, Connection) end; certify(internal, #server_hello_done{}, - #state{session = #session{master_secret = undefined}, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = undefined}, ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, negotiated_version = {Major, Minor} = Version, psk_identity = PSKIdentity, premaster_secret = undefined, - role = client, key_algorithm = Alg} = State0, Connection) when Alg == rsa_psk -> Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), @@ -900,11 +947,11 @@ certify(internal, #server_hello_done{}, end; %% Master secret was determined with help of server-key exchange msg certify(internal, #server_hello_done{}, - #state{session = #session{master_secret = MasterSecret} = Session, + #state{static_env = #static_env{role = client}, + session = #session{master_secret = MasterSecret} = Session, connection_states = ConnectionStates0, negotiated_version = Version, - premaster_secret = undefined, - role = client} = State0, Connection) -> + premaster_secret = undefined} = State0, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> @@ -915,11 +962,11 @@ certify(internal, #server_hello_done{}, end; %% Master secret is calculated from premaster_secret certify(internal, #server_hello_done{}, - #state{session = Session0, + #state{static_env = #static_env{role = client}, + session = Session0, connection_states = ConnectionStates0, negotiated_version = Version, - premaster_secret = PremasterSecret, - role = client} = State0, Connection) -> + premaster_secret = PremasterSecret} = State0, Connection) -> case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, client) of {MasterSecret, ConnectionStates} -> @@ -931,7 +978,7 @@ certify(internal, #server_hello_done{}, handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) end; certify(internal = Type, #client_key_exchange{} = Msg, - #state{role = server, + #state{static_env = #static_env{role = server}, client_certificate_requested = true, ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, Connection) -> @@ -961,49 +1008,49 @@ cipher(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); cipher(internal, #certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, - #state{role = server, + #state{static_env = #static_env{role = server}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, key_algorithm = KexAlg, public_key_info = PublicKeyInfo, negotiated_version = Version, - session = #session{master_secret = MasterSecret}, - tls_handshake_history = Handshake - } = State0, Connection) -> + session = #session{master_secret = MasterSecret} + } = State, Connection) -> TLSVersion = ssl:tls_version(Version), %% Use negotiated value if TLS-1.2 otherwhise return default HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, TLSVersion), case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, - TLSVersion, HashSign, MasterSecret, Handshake) of + TLSVersion, HashSign, MasterSecret, Hist) of valid -> - {Record, State} = Connection:next_record(State0), - Connection:next_event(?FUNCTION_NAME, Record, + Connection:next_event(?FUNCTION_NAME, no_record, State#state{cert_hashsign_algorithm = HashSign}); #alert{} = Alert -> - handle_own_alert(Alert, Version, ?FUNCTION_NAME, State0) + handle_own_alert(Alert, Version, ?FUNCTION_NAME, State) end; %% client must send a next protocol message if we are expecting it cipher(internal, #finished{}, - #state{role = server, expecting_next_protocol_negotiation = true, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true, negotiated_protocol = undefined, negotiated_version = Version} = State0, _Connection) -> handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, ?FUNCTION_NAME, State0); cipher(internal, #finished{verify_data = Data} = Finished, - #state{negotiated_version = Version, - host = Host, - port = Port, - role = Role, - expecting_finished = true, + #state{static_env = #static_env{role = Role, + host = Host, + port = Port}, + negotiated_version = Version, + expecting_finished = true, session = #session{master_secret = MasterSecret} = Session0, ssl_options = SslOpts, connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State, Connection) -> + handshake_env = #handshake_env{tls_handshake_history = Hist}} = State, Connection) -> case ssl_handshake:verify_connection(ssl:tls_version(Version), Finished, opposite_role(Role), get_current_prf(ConnectionStates0, read), - MasterSecret, Handshake0) of + MasterSecret, Hist) of verified -> - Session = register_session(Role, host_id(Role, Host, SslOpts), Port, Session0), + Session = handle_session(Role, SslOpts, Host, Port, Session0), cipher_role(Role, Data, Session, State#state{expecting_finished = false}, Connection); #alert{} = Alert -> @@ -1012,19 +1059,20 @@ cipher(internal, #finished{verify_data = Data} = Finished, %% only allowed to send next_protocol message after change cipher spec %% & before finished message and it is not allowed during renegotiation cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true, + #state{static_env = #static_env{role = server}, + expecting_next_protocol_negotiation = true, expecting_finished = true} = State0, Connection) -> {Record, State} = Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_next_protocol_negotiation = false}); cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = - State0, Connection) -> - ConnectionStates1 = + State, Connection) -> + ConnectionStates = ssl_record:activate_pending_connection_state(ConnectionStates0, read, Connection), - {Record, State} = Connection:next_record(State0#state{connection_states = - ConnectionStates1}), - Connection:next_event(?FUNCTION_NAME, Record, State#state{expecting_finished = true}); + Connection:next_event(?FUNCTION_NAME, no_record, State#state{connection_states = + ConnectionStates, + expecting_finished = true}); cipher(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). @@ -1034,15 +1082,18 @@ cipher(Type, Msg, State, Connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- connection({call, RecvFrom}, {recv, N, Timeout}, - #state{protocol_cb = Connection, socket_options = - #socket_options{active = false}} = State0, Connection) -> + #state{static_env = #static_env{protocol_cb = Connection}, + socket_options = + #socket_options{active = false}} = State0, Connection) -> Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - Connection:passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom, - timer = Timer}, ?FUNCTION_NAME); -connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, + passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom, + timer = Timer}, ?FUNCTION_NAME, Connection); + +connection({call, From}, renegotiate, #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv} = State, Connection) -> - Connection:renegotiate(State#state{renegotiation = {true, From}}, []); + Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, From}}}, []); connection({call, From}, peer_certificate, #state{session = #session{peer_certificate = Cert}} = State, _) -> hibernate_after(?FUNCTION_NAME, State, [{reply, From, {ok, Cert}}]); @@ -1061,27 +1112,27 @@ connection({call, From}, negotiated_protocol, [{reply, From, {ok, SelectedProtocol}}]); connection({call, From}, Msg, State, Connection) -> handle_call(Msg, From, ?FUNCTION_NAME, State, Connection); -connection(cast, {internal_renegotiate, WriteState}, #state{protocol_cb = Connection, +connection(cast, {internal_renegotiate, WriteState}, #state{static_env = #static_env{protocol_cb = Connection}, + handshake_env = HsEnv, connection_states = ConnectionStates} = State, Connection) -> - Connection:renegotiate(State#state{renegotiation = {true, internal}, + Connection:renegotiate(State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, internal}}, connection_states = ConnectionStates#{current_write => WriteState}}, []); connection(cast, {dist_handshake_complete, DHandle}, #state{ssl_options = #ssl_options{erl_dist = true}, - erl_dist_data = ErlDistData, socket_options = SockOpts} = State0, Connection) -> process_flag(priority, normal), State1 = State0#state{ - socket_options = - SockOpts#socket_options{active = true}, - erl_dist_data = ErlDistData#{dist_handle => DHandle}}, - {Record, State} = dist_app_data(<<>>, State1), + socket_options = SockOpts#socket_options{active = true}, + erl_dist_handle = DHandle, + bytes_to_read = undefined}, + {Record, State} = read_application_data(<<>>, State1), Connection:next_event(connection, Record, State); connection(info, Msg, State, _) -> handle_info(Msg, ?FUNCTION_NAME, State); connection(internal, {recv, _}, State, Connection) -> - Connection:passive_receive(State, ?FUNCTION_NAME); + passive_receive(State, ?FUNCTION_NAME, Connection); connection(Type, Msg, State, Connection) -> handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). @@ -1090,16 +1141,6 @@ connection(Type, Msg, State, Connection) -> #state{}, tls_connection | dtls_connection) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- -downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, - #state{transport_cb = Transport, socket = Socket, - downgrade = {Pid, From}} = State, _) -> - tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), - Transport:controlling_process(Socket, Pid), - gen_statem:reply(From, {ok, Socket}), - stop(normal, State); -downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) -> - gen_statem:reply(From, {error, timeout}), - stop(normal, State); downgrade(Type, Event, State, Connection) -> handle_common_event(Type, Event, ?FUNCTION_NAME, State, Connection). @@ -1108,14 +1149,17 @@ downgrade(Type, Event, State, Connection) -> %% common or unexpected events for the state. %%-------------------------------------------------------------------- handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, - #state{role = client} = State, _) -> + #state{static_env = #static_env{role = client}, + handshake_env = HsEnv} = State, _) -> %% Should not be included in handshake history - {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]}; -handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _) + {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}}}, + [{next_event, internal, Handshake}]}; +handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, + #state{static_env = #static_env{role = client}}, _) when StateName =/= connection -> - {keep_state_and_data}; + keep_state_and_data; handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, - #state{tls_handshake_history = Hs0} = State0, + #state{handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv} = State0, Connection) -> PossibleSNI = Connection:select_sni_extension(Handshake), @@ -1123,27 +1167,14 @@ handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, %% a client_hello, which needs to be determined by the connection callback. %% In other cases this is a noop State = handle_sni_extension(PossibleSNI, State0), - HsHist = ssl_handshake:update_handshake_history(Hs0, iolist_to_binary(Raw)), - {next_state, StateName, State#state{tls_handshake_history = HsHist}, + + Hist = ssl_handshake:update_handshake_history(Hist0, Raw), + {next_state, StateName, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}}, [{next_event, internal, Handshake}]}; handle_common_event(internal, {protocol_record, TLSorDTLSRecord}, StateName, State, Connection) -> - Connection:handle_common_event(internal, TLSorDTLSRecord, StateName, State); + Connection:handle_protocol_record(TLSorDTLSRecord, StateName, State); handle_common_event(timeout, hibernate, _, _, _) -> {keep_state_and_data, [hibernate]}; -handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) -> - case read_application_data(Data, State0) of - {stop, _, _} = Stop-> - Stop; - {Record, State} -> - case Connection:next_event(StateName, Record, State) of - {next_state, StateName, State} -> - hibernate_after(StateName, State, []); - {next_state, StateName, State, Actions} -> - hibernate_after(StateName, State, Actions); - {stop, _, _} = Stop -> - Stop - end - end; handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, #state{negotiated_version = Version} = State, _) -> handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, @@ -1156,39 +1187,37 @@ handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} handle_call({application_data, _Data}, _, _, _, _) -> %% In renegotiation priorities handshake, send data when handshake is finished {keep_state_and_data, [postpone]}; -handle_call({close, {Pid, Timeout}}, From, StateName, State0, Connection) when is_pid(Pid) -> - %% terminate will send close alert to peer - State = State0#state{downgrade = {Pid, From}}, - Connection:terminate(downgrade, StateName, State), - %% User downgrades connection - %% When downgrading an TLS connection to a transport connection - %% we must recive the close alert from the peer before releasing the - %% transport socket. - {next_state, downgrade, State#state{terminated = true}, [{timeout, Timeout, downgrade}]}; handle_call({close, _} = Close, From, StateName, State, _Connection) -> %% Run terminate before returning so that the reuseaddr %% inet-option works properly Result = terminate(Close, StateName, State), - stop_and_reply( - {shutdown, normal}, - {reply, From, Result}, State#state{terminated = true}); + {stop_and_reply, + {shutdown, normal}, + {reply, From, Result}, State#state{terminated = true}}; +handle_call({shutdown, read_write = How}, From, StateName, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State, _) -> + try send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + StateName, State) of + _ -> + case Transport:shutdown(Socket, How) of + ok -> + {next_state, StateName, State#state{terminated = true}, [{reply, From, ok}]}; + Error -> + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State#state{terminated = true}} + end + catch + throw:Return -> + Return + end; handle_call({shutdown, How0}, From, StateName, - #state{transport_cb = Transport, - socket = Socket} = State, _) -> - case How0 of - How when How == write; How == both -> - send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - StateName, State); - _ -> - ok - end, - + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}} = State, _) -> case Transport:shutdown(Socket, How0) of ok -> - {keep_state_and_data, [{reply, From, ok}]}; + {next_state, StateName, State, [{reply, From, ok}]}; Error -> - gen_statem:reply(From, {error, Error}), - stop(normal, State) + {stop_and_reply, {shutdown, normal}, {reply, From, Error}, State} end; handle_call({recv, _N, _Timeout}, From, _, #state{socket_options = @@ -1208,15 +1237,16 @@ handle_call({new_user, User}, From, StateName, {next_state, StateName, State#state{user_application = {NewMon,User}}, [{reply, From, ok}]}; handle_call({get_opts, OptTags}, From, _, - #state{socket = Socket, - transport_cb = Transport, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, socket_options = SockOpts}, Connection) -> OptsReply = get_socket_opts(Connection, Transport, Socket, OptTags, SockOpts, []), {keep_state_and_data, [{reply, From, OptsReply}]}; handle_call({set_opts, Opts0}, From, StateName, - #state{socket_options = Opts1, - socket = Socket, - transport_cb = Transport} = State0, Connection) -> + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, + socket_options = Opts1 + } = State0, Connection) -> {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), State = State0#state{socket_options = Opts}, handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); @@ -1257,22 +1287,25 @@ handle_call(_,_,_,_,_) -> {keep_state_and_data, [postpone]}. handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{socket = Socket, transport_cb = Transport, - protocol_cb = Connection, - start_or_recv_from = StartFrom, role = Role, - error_tag = ErrorTag, - tracker = Tracker} = State) when StateName =/= connection -> + #state{static_env = #static_env{role = Role, + socket = Socket, + transport_cb = Transport, + error_tag = ErrorTag, + tracker = Tracker, + protocol_cb = Connection}, + start_or_recv_from = StartFrom + } = State) when StateName =/= connection -> Pids = Connection:pids(State), alert_user(Pids, Transport, Tracker,Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection), - stop(normal, State); + {stop, {shutdown, normal}, State}; -handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, - error_tag = ErrorTag} = State) -> +handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket, + error_tag = ErrorTag}} = State) -> Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), ?LOG_ERROR(Report), handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - stop(normal, State); + {stop, {shutdown,normal}, State}; handle_info({'DOWN', MonitorRef, _, _, Reason}, _, #state{user_application = {MonitorRef, _Pid}, @@ -1280,7 +1313,7 @@ handle_info({'DOWN', MonitorRef, _, _, Reason}, _, {stop, {shutdown, Reason}}; handle_info({'DOWN', MonitorRef, _, _, _}, _, #state{user_application = {MonitorRef, _Pid}}) -> - {stop, normal}; + {stop, {shutdown, normal}}; handle_info({'EXIT', Pid, _Reason}, StateName, #state{user_application = {_MonitorRef, Pid}} = State) -> %% It seems the user application has linked to us @@ -1288,22 +1321,22 @@ handle_info({'EXIT', Pid, _Reason}, StateName, {next_state, StateName, State}; %%% So that terminate will be run when supervisor issues shutdown handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> - stop(shutdown, State); -handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) -> + {stop, shutdown, State}; +handle_info({'EXIT', Socket, normal}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> %% Handle as transport close" - stop({shutdown, transport_closed}, State); -handle_info({'EXIT', Socket, Reason}, _StateName, #state{socket = Socket} = State) -> - stop({shutdown, Reason}, State); + {stop,{shutdown, transport_closed}, State}; +handle_info({'EXIT', Socket, Reason}, _StateName, #state{static_env = #static_env{socket = Socket}} = State) -> + {stop,{shutdown, Reason}, State}; handle_info(allow_renegotiate, StateName, State) -> {next_state, StateName, State#state{allow_renegotiate = true}}; handle_info({cancel_start_or_recv, StartFrom}, StateName, - #state{renegotiation = {false, first}} = State) when StateName =/= connection -> - stop_and_reply( - {shutdown, user_timeout}, - {reply, StartFrom, {error, timeout}}, - State#state{timer = undefined}); + #state{handshake_env = #handshake_env{renegotiation = {false, first}}} = State) when StateName =/= connection -> + {stop_and_reply, + {shutdown, user_timeout}, + {reply, StartFrom, {error, timeout}}, + State#state{timer = undefined}}; handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined -> {next_state, StateName, State#state{start_or_recv_from = undefined, @@ -1312,7 +1345,7 @@ handle_info({cancel_start_or_recv, RecvFrom}, StateName, handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> {next_state, StateName, State#state{timer = undefined}}; -handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) -> +handle_info(Msg, StateName, #state{static_env = #static_env{socket = Socket, error_tag = Tag}} = State) -> Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]), ?LOG_NOTICE(Report), {next_state, StateName, State}. @@ -1329,14 +1362,15 @@ terminate(_, _, #state{terminated = true}) -> %% before run by gen_statem which will end up here ok; terminate({shutdown, transport_closed} = Reason, - _StateName, #state{protocol_cb = Connection, - socket = Socket, transport_cb = Transport} = State) -> + _StateName, #state{static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined); terminate({shutdown, own_alert}, _StateName, #state{ - protocol_cb = Connection, - socket = Socket, - transport_cb = Transport} = State) -> + static_env = #static_env{protocol_cb = Connection, + socket = Socket, + transport_cb = Transport}} = State) -> handle_trusted_certs_db(State), case application:get_env(ssl, alert_timeout) of {ok, Timeout} when is_integer(Timeout) -> @@ -1344,23 +1378,27 @@ terminate({shutdown, own_alert}, _StateName, #state{ _ -> Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) end; -terminate(downgrade = Reason, connection, #state{protocol_cb = Connection, - transport_cb = Transport, socket = Socket - } = State) -> +terminate({shutdown, downgrade = Reason}, downgrade, #state{static_env = #static_env{protocol_cb = Connection, + transport_cb = Transport, + socket = Socket} + } = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined); -terminate(Reason, connection, #state{protocol_cb = Connection, - connection_states = ConnectionStates, - ssl_options = #ssl_options{padding_check = Check}, - transport_cb = Transport, socket = Socket - } = State) -> +terminate(Reason, connection, #state{static_env = #static_env{ + protocol_cb = Connection, + transport_cb = Transport, + socket = Socket}, + connection_states = ConnectionStates, + ssl_options = #ssl_options{padding_check = Check} + } = State) -> handle_trusted_certs_db(State), Alert = terminate_alert(Reason), %% Send the termination ALERT if possible catch (ok = Connection:send_alert_in_connection(Alert, State)), - Connection:close(Reason, Socket, Transport, ConnectionStates, Check); -terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection, - socket = Socket + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +terminate(Reason, _StateName, #state{static_env = #static_env{transport_cb = Transport, + protocol_cb = Connection, + socket = Socket} } = State) -> handle_trusted_certs_db(State), Connection:close(Reason, Socket, Transport, undefined, undefined). @@ -1379,7 +1417,7 @@ format_status(terminate, [_, StateName, State]) -> [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, protocol_buffers = ?SECRET_PRINTOUT, user_data_buffer = ?SECRET_PRINTOUT, - tls_handshake_history = ?SECRET_PRINTOUT, + handshake_env = ?SECRET_PRINTOUT, session = ?SECRET_PRINTOUT, private_key = ?SECRET_PRINTOUT, diffie_hellman_params = ?SECRET_PRINTOUT, @@ -1394,15 +1432,15 @@ format_status(terminate, [_, StateName, State]) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -send_alert(Alert, connection, #state{protocol_cb = Connection} = State) -> +send_alert(Alert, connection, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> Connection:send_alert_in_connection(Alert, State); -send_alert(Alert, _, #state{protocol_cb = Connection} = State) -> +send_alert(Alert, _, #state{static_env = #static_env{protocol_cb = Connection}} = State) -> Connection:send_alert(Alert, State). -connection_info(#state{sni_hostname = SNIHostname, - session = #session{session_id = SessionId, +connection_info(#state{static_env = #static_env{protocol_cb = Connection}, + sni_hostname = SNIHostname, + session = #session{session_id = SessionId, cipher_suite = CipherSuite, ecc = ECCCurve}, - protocol_cb = Connection, negotiated_version = {_,_} = Version, ssl_options = Opts}) -> RecordCB = record_cb(Connection), @@ -1514,13 +1552,12 @@ new_server_hello(#server_hello{cipher_suite = CipherSuite, negotiated_version = Version} = State0, Connection) -> try server_certify_and_key_exchange(State0, Connection) of #state{} = State1 -> - {State2, Actions} = server_hello_done(State1, Connection), + {State, Actions} = server_hello_done(State1, Connection), Session = Session0#session{session_id = SessionId, cipher_suite = CipherSuite, compression_method = Compression}, - {Record, State} = Connection:next_record(State2#state{session = Session}), - Connection:next_event(certify, Record, State, Actions) + Connection:next_event(certify, no_record, State#state{session = Session}, Actions) catch #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0) @@ -1535,10 +1572,9 @@ resumed_server_hello(#state{session = Session, {_, ConnectionStates1} -> State1 = State0#state{connection_states = ConnectionStates1, session = Session}, - {State2, Actions} = + {State, Actions} = finalize_handshake(State1, abbreviated, Connection), - {Record, State} = Connection:next_record(State2), - Connection:next_event(abbreviated, Record, State, Actions); + Connection:next_event(abbreviated, no_record, State, Actions); #alert{} = Alert -> handle_own_alert(Alert, Version, hello, State0) end. @@ -1560,10 +1596,8 @@ handle_peer_cert(Role, PeerCert, PublicKeyInfo, Session#session{peer_certificate = PeerCert}, public_key_info = PublicKeyInfo}, #{key_exchange := KeyAlgorithm} = ssl_cipher_format:suite_definition(CipherSuite), - State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), - - {Record, State} = Connection:next_record(State2), - Connection:next_event(certify, Record, State). + State = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlgorithm, State1), + Connection:next_event(certify, no_record, State). handle_peer_cert_key(client, _, {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, @@ -1586,9 +1620,10 @@ handle_peer_cert_key(client, _, handle_peer_cert_key(_, _, _, _, State) -> State. -certify_client(#state{client_certificate_requested = true, role = client, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +certify_client(#state{static_env = #static_env{role = client, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + client_certificate_requested = true, session = #session{own_certificate = OwnCert}} = State, Connection) -> Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), @@ -1596,16 +1631,17 @@ certify_client(#state{client_certificate_requested = true, role = client, certify_client(#state{client_certificate_requested = false} = State, _) -> State. -verify_client_cert(#state{client_certificate_requested = true, role = client, +verify_client_cert(#state{static_env = #static_env{role = client}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, + client_certificate_requested = true, negotiated_version = Version, private_key = PrivateKey, session = #session{master_secret = MasterSecret, own_certificate = OwnCert}, - cert_hashsign_algorithm = HashSign, - tls_handshake_history = Handshake0} = State, Connection) -> + cert_hashsign_algorithm = HashSign} = State, Connection) -> case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, - ssl:tls_version(Version), HashSign, PrivateKey, Handshake0) of + ssl:tls_version(Version), HashSign, PrivateKey, Hist) of #certificate_verify{} = Verified -> Connection:queue_handshake(Verified, State); ignore -> @@ -1621,11 +1657,10 @@ client_certify_and_key_exchange(#state{negotiated_version = Version} = try do_client_certify_and_key_exchange(State0, Connection) of State1 = #state{} -> {State2, Actions} = finalize_handshake(State1, certify, Connection), - State3 = State2#state{ - %% Reinitialize - client_certificate_requested = false}, - {Record, State} = Connection:next_record(State3), - Connection:next_event(cipher, Record, State, Actions) + State = State2#state{ + %% Reinitialize + client_certificate_requested = false}, + Connection:next_event(cipher, no_record, State, Actions) catch throw:#alert{} = Alert -> handle_own_alert(Alert, Version, certify, State0) @@ -1642,7 +1677,9 @@ server_certify_and_key_exchange(State0, Connection) -> request_client_cert(State2, Connection). certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{private_key = Key, client_hello_version = {Major, Minor} = Version} = State, Connection) -> + #state{private_key = Key, + handshake_env = #handshake_env{client_hello_version = {Major, Minor} = Version}} + = State, Connection) -> FakeSecret = make_premaster_secret(Version, rsa), %% Countermeasure for Bleichenbacher attack always provide some kind of premaster secret %% and fail handshake later.RFC 5246 section 7.4.7.1. @@ -1717,8 +1754,8 @@ certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon; Algo == ecdhe_psk; Algo == srp_anon -> State; -certify_server(#state{cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +certify_server(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, session = #session{own_certificate = OwnCert}} = State, Connection) -> case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of Cert = #certificate{} -> @@ -1727,9 +1764,9 @@ certify_server(#state{cert_db = CertDbHandle, throw(Alert) end. -key_exchange(#state{role = server, key_algorithm = rsa} = State,_) -> +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa} = State,_) -> State; -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, diffie_hellman_params = #'DHParameter'{} = Params, private_key = PrivateKey, @@ -1750,12 +1787,14 @@ key_exchange(#state{role = server, key_algorithm = Algo, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; -key_exchange(#state{role = server, private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, + private_key = #'ECPrivateKey'{parameters = ECCurve} = Key, + key_algorithm = Algo, session = Session} = State, _) when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> State#state{diffie_hellman_keys = Key, session = Session#session{ecc = ECCurve}}; -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, session = #session{ecc = ECCCurve}, @@ -1777,10 +1816,10 @@ key_exchange(#state{role = server, key_algorithm = Algo, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = ECDHKeys}; -key_exchange(#state{role = server, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{role = server, key_algorithm = psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1797,7 +1836,7 @@ key_exchange(#state{role = server, key_algorithm = psk, ServerRandom, PrivateKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = server, key_algorithm = dhe_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = dhe_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, diffie_hellman_params = #'DHParameter'{} = Params, @@ -1818,7 +1857,7 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = DHKeys}; -key_exchange(#state{role = server, key_algorithm = ecdhe_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = ecdhe_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1839,10 +1878,10 @@ key_exchange(#state{role = server, key_algorithm = ecdhe_psk, PrivateKey}), State = Connection:queue_handshake(Msg, State0), State#state{diffie_hellman_keys = ECDHKeys}; -key_exchange(#state{role = server, key_algorithm = rsa_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> State; -key_exchange(#state{role = server, key_algorithm = rsa_psk, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = rsa_psk, ssl_options = #ssl_options{psk_identity = PskIdentityHint}, hashsign_algorithm = HashSignAlgo, private_key = PrivateKey, @@ -1859,7 +1898,7 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk, ServerRandom, PrivateKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = server, key_algorithm = Algo, +key_exchange(#state{static_env = #static_env{role = server}, key_algorithm = Algo, ssl_options = #ssl_options{user_lookup_fun = LookupFun}, hashsign_algorithm = HashSignAlgo, session = #session{srp_username = Username}, @@ -1889,14 +1928,14 @@ key_exchange(#state{role = server, key_algorithm = Algo, State = Connection:queue_handshake(Msg, State0), State#state{srp_params = SrpParams, srp_keys = Keys}; -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = rsa, public_key_info = PublicKeyInfo, negotiated_version = Version, premaster_secret = PremasterSecret} = State0, Connection) -> Msg = rsa_key_exchange(ssl:tls_version(Version), PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, diffie_hellman_keys = {DhPubKey, _} @@ -1907,7 +1946,7 @@ key_exchange(#state{role = client, Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {dh, DhPubKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, session = Session, @@ -1917,14 +1956,14 @@ key_exchange(#state{role = client, Algorithm == ecdh_anon -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {ecdh, Key}), Connection:queue_handshake(Msg, State0#state{session = Session#session{ecc = ECCurve}}); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = psk, negotiated_version = Version} = State0, Connection) -> Msg = ssl_handshake:key_exchange(client, ssl:tls_version(Version), {psk, SslOpts#ssl_options.psk_identity}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = dhe_psk, negotiated_version = Version, @@ -1934,7 +1973,7 @@ key_exchange(#state{role = client, SslOpts#ssl_options.psk_identity, DhPubKey}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = ecdhe_psk, negotiated_version = Version, @@ -1944,7 +1983,7 @@ key_exchange(#state{role = client, SslOpts#ssl_options.psk_identity, ECDHKeys}), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, ssl_options = SslOpts, key_algorithm = rsa_psk, public_key_info = PublicKeyInfo, @@ -1954,7 +1993,7 @@ key_exchange(#state{role = client, Msg = rsa_psk_key_exchange(ssl:tls_version(Version), SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), Connection:queue_handshake(Msg, State0); -key_exchange(#state{role = client, +key_exchange(#state{static_env = #static_env{role = client}, key_algorithm = Algorithm, negotiated_version = Version, srp_keys = {ClientPubKey, _}} @@ -2004,11 +2043,11 @@ request_client_cert(#state{key_algorithm = Alg} = State, _) Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> State; -request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer, - signature_algs = SupportedHashSigns}, - connection_states = ConnectionStates0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, +request_client_cert(#state{static_env = #static_env{cert_db = CertDbHandle, + cert_db_ref = CertDbRef}, + ssl_options = #ssl_options{verify = verify_peer, + signature_algs = SupportedHashSigns}, + connection_states = ConnectionStates0, negotiated_version = Version} = State0, Connection) -> #{security_parameters := #security_parameters{cipher_suite = CipherSuite}} = @@ -2034,10 +2073,9 @@ calculate_master_secret(PremasterSecret, ConnectionStates0, server) of {MasterSecret, ConnectionStates} -> Session = Session0#session{master_secret = MasterSecret}, - State1 = State0#state{connection_states = ConnectionStates, + State = State0#state{connection_states = ConnectionStates, session = Session}, - {Record, State} = Connection:next_record(State1), - Connection:next_event(Next, Record, State); + Connection:next_event(Next, no_record, State); #alert{} = Alert -> handle_own_alert(Alert, Version, certify, State0) end. @@ -2054,7 +2092,7 @@ finalize_handshake(State0, StateName, Connection) -> State = next_protocol(State2, Connection), finished(State, StateName, Connection). -next_protocol(#state{role = server} = State, _) -> +next_protocol(#state{static_env = #static_env{role = server}} = State, _) -> State; next_protocol(#state{negotiated_protocol = undefined} = State, _) -> State; @@ -2067,14 +2105,16 @@ next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) - cipher_protocol(State, Connection) -> Connection:queue_change_cipher(#change_cipher_spec{}, State). -finished(#state{role = Role, negotiated_version = Version, +finished(#state{static_env = #static_env{role = Role}, + handshake_env = #handshake_env{tls_handshake_history = Hist}, + negotiated_version = Version, session = Session, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State0, StateName, Connection) -> + connection_states = ConnectionStates0} = State0, + StateName, Connection) -> MasterSecret = Session#session.master_secret, Finished = ssl_handshake:finished(ssl:tls_version(Version), Role, get_current_prf(ConnectionStates0, write), - MasterSecret, Handshake0), + MasterSecret, Hist), ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), Connection:send_handshake(Finished, State0#state{connection_states = ConnectionStates}). @@ -2110,10 +2150,9 @@ calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, calculate_secret(#server_psk_params{ hint = IdentityHint}, - State0, Connection) -> + State, Connection) -> %% store for later use - {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}), - Connection:next_event(certify, Record, State); + Connection:next_event(certify, no_record, State#state{psk_identity = IdentityHint}); calculate_secret(#server_dhe_psk_params{ dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, @@ -2147,8 +2186,9 @@ calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKe master_secret(#alert{} = Alert, _) -> Alert; -master_secret(PremasterSecret, #state{session = Session, - negotiated_version = Version, role = Role, +master_secret(PremasterSecret, #state{static_env = #static_env{role = Role}, + session = Session, + negotiated_version = Version, connection_states = ConnectionStates0} = State) -> case ssl_handshake:master_secret(ssl:tls_version(Version), PremasterSecret, ConnectionStates0, Role) of @@ -2366,18 +2406,18 @@ handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>, cacerts = []}}) -> %% No trusted certs specified ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - cert_db = CertDb, - ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined -> - %% Certs provided as DER directly cannot be shared +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + cert_db = CertDb}, + ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined -> + %% Certs provided as DER directly can not be shared %% with other connections and it is safe to delete them when the connection ends. ssl_pkix_db:remove_trusted_certs(Ref, CertDb); -handle_trusted_certs_db(#state{file_ref_db = undefined}) -> +handle_trusted_certs_db(#state{static_env = #static_env{file_ref_db = undefined}}) -> %% Something went wrong early (typically cacertfile does not %% exist) so there is nothing to handle ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - file_ref_db = RefDb, +handle_trusted_certs_db(#state{static_env = #static_env{cert_db_ref = Ref, + file_ref_db = RefDb}, ssl_options = #ssl_options{cacertfile = File}}) -> case ssl_pkix_db:ref_count(Ref, RefDb, -1) of 0 -> @@ -2386,29 +2426,28 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref, ok end. -prepare_connection(#state{renegotiation = Renegotiate, +prepare_connection(#state{handshake_env = #handshake_env{renegotiation = Renegotiate}, start_or_recv_from = RecvFrom} = State0, Connection) when Renegotiate =/= {false, first}, RecvFrom =/= undefined -> - State1 = Connection:reinit(State0), - {Record, State} = Connection:next_record(State1), - {Record, ack_connection(State)}; + State = Connection:reinit(State0), + {no_record, ack_connection(State)}; prepare_connection(State0, Connection) -> State = Connection:reinit(State0), {no_record, ack_connection(State)}. -ack_connection(#state{renegotiation = {true, Initiater}} = State) when Initiater == peer; - Initiater == internal -> - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {true, From}} = State) -> +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, Initiater}} = HsEnv} = State) when Initiater == peer; + Initiater == internal -> + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {true, From}} = HsEnv} = State) -> gen_statem:reply(From, ok), - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {false, first}, + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}}; +ack_connection(#state{handshake_env = #handshake_env{renegotiation = {false, first}} = HsEnv, start_or_recv_from = StartFrom, timer = Timer} = State) when StartFrom =/= undefined -> gen_statem:reply(StartFrom, connected), cancel_timer(Timer), - State#state{renegotiation = undefined, + State#state{handshake_env = HsEnv#handshake_env{renegotiation = undefined}, start_or_recv_from = undefined, timer = undefined}; ack_connection(State) -> State. @@ -2424,15 +2463,35 @@ session_handle_params(#server_ecdh_params{curve = ECCurve}, Session) -> session_handle_params(_, Session) -> Session. -register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> +handle_session(Role = server, #ssl_options{reuse_sessions = true} = SslOpts, + Host, Port, Session0) -> + register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, true); +handle_session(Role = client, #ssl_options{verify = verify_peer, + reuse_sessions = Reuse} = SslOpts, + Host, Port, Session0) when Reuse =/= false -> + register_session(Role, host_id(Role, Host, SslOpts), Port, Session0, reg_type(Reuse)); +handle_session(server, _, Host, Port, Session) -> + %% Remove "session of type new" entry from session DB + ssl_manager:invalidate_session(Host, Port, Session), + Session; +handle_session(client, _,_,_, Session) -> + %% In client case there is no entry yet, so nothing to remove + Session. + +reg_type(save) -> + true; +reg_type(true) -> + unique. + +register_session(client, Host, Port, #session{is_resumable = new} = Session0, Save) -> Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Host, Port, Session), + ssl_manager:register_session(Host, Port, Session, Save), Session; -register_session(server, _, Port, #session{is_resumable = new} = Session0) -> +register_session(server, _, Port, #session{is_resumable = new} = Session0, _) -> Session = Session0#session{is_resumable = true}, ssl_manager:register_session(Port, Session), Session; -register_session(_, _, _, Session) -> +register_session(_, _, _, Session, _) -> Session. %% Already registered host_id(client, _Host, #ssl_options{server_name_indication = Hostname}) when is_list(Hostname) -> @@ -2441,31 +2500,31 @@ host_id(_, Host, _) -> Host. handle_new_session(NewId, CipherSuite, Compression, - #state{session = Session0, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{protocol_cb = Connection}, + session = Session0 + } = State0) -> Session = Session0#session{session_id = NewId, cipher_suite = CipherSuite, compression_method = Compression}, - {Record, State} = Connection:next_record(State0#state{session = Session}), - Connection:next_event(certify, Record, State). - -handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, - negotiated_version = Version, - host = Host, port = Port, - protocol_cb = Connection, - session_cache = Cache, - session_cache_cb = CacheCb} = State0) -> + Connection:next_event(certify, no_record, State0#state{session = Session}). + +handle_resumed_session(SessId, #state{static_env = #static_env{host = Host, + port = Port, + protocol_cb = Connection, + session_cache = Cache, + session_cache_cb = CacheCb}, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State) -> Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), case ssl_handshake:master_secret(ssl:tls_version(Version), Session, ConnectionStates0, client) of {_, ConnectionStates} -> - {Record, State} = - Connection:next_record(State0#state{ - connection_states = ConnectionStates, - session = Session}), - Connection:next_event(abbreviated, Record, State); + Connection:next_event(abbreviated, no_record, State#state{ + connection_states = ConnectionStates, + session = Session}); #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) + handle_own_alert(Alert, Version, hello, State) end. make_premaster_secret({MajVer, MinVer}, rsa) -> @@ -2513,12 +2572,9 @@ ssl_options_list([Key | Keys], [Value | Values], Acc) -> handle_active_option(false, connection = StateName, To, Reply, State) -> hibernate_after(StateName, State, [{reply, To, Reply}]); -handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection, - user_data_buffer = <<>>} = State0) -> - %% Need data, set active once - {Record, State1} = Connection:next_record_if_active(State0), - %% Note: Renogotiation may cause StateName0 =/= StateName - case Connection:next_event(StateName0, Record, State1) of +handle_active_option(_, connection = StateName0, To, Reply, #state{static_env = #static_env{protocol_cb = Connection}, + user_data_buffer = <<>>} = State0) -> + case Connection:next_event(StateName0, no_record, State0) of {next_state, StateName, State} -> hibernate_after(StateName, State, [{reply, To, Reply}]); {next_state, StateName, State, Actions} -> @@ -2531,7 +2587,8 @@ handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = {next_state, StateName, State, [{reply, To, Reply}]}; %% user_data_buffer =/= <<>> -handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) -> +handle_active_option(_, StateName0, To, Reply, + #state{static_env = #static_env{protocol_cb = Connection}} = State0) -> case read_application_data(<<>>, State0) of {stop, _, _} = Stop -> Stop; @@ -2593,21 +2650,28 @@ decode_packet(Type, Buffer, PacketOpts) -> %% 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(CPids, Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, - Data, Pid, From, Tracker, Connection) -> - send_or_reply(Active, Pid, From, - format_reply(CPids, 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, +deliver_app_data( + CPids, Transport, Socket, + #socket_options{active=Active, packet=Type} = SOpts, + Data, Pid, From, Tracker, Connection) -> + %% + send_or_reply( + Active, Pid, From, + format_reply( + CPids, 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}; @@ -2712,7 +2776,7 @@ invalidate_session(server, _, Port, Session) -> handle_sni_extension(undefined, State) -> State; -handle_sni_extension(#sni{hostname = Hostname}, State0) -> +handle_sni_extension(#sni{hostname = Hostname}, #state{static_env = #static_env{role = Role} = InitStatEnv0} = State0) -> NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), case NewOptions of undefined -> @@ -2726,14 +2790,16 @@ handle_sni_extension(#sni{hostname = Hostname}, State0) -> private_key := Key, dh_params := DHParams, own_certificate := OwnCert}} = - ssl_config:init(NewOptions, State0#state.role), + ssl_config:init(NewOptions, 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, + static_env = InitStatEnv0#static_env{ + 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, @@ -2762,13 +2828,10 @@ new_emulated([], EmOpts) -> new_emulated(NewEmOpts, _) -> NewEmOpts. -stop(Reason, State) -> - {stop, Reason, State}. - -stop_and_reply(Reason, Replies, State) -> - {stop_and_reply, Reason, Replies, State}. - -is_dist_up(#{dist_handle := Handle}) when Handle =/= undefined -> - true; -is_dist_up(_) -> - false. +-compile({inline, [bincat/2]}). +bincat(<<>>, B) -> + B; +bincat(A, <<>>) -> + A; +bincat(A, B) -> + <<A/binary, B/binary>>. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl index 91467e9b26..dd3bdd7478 100644 --- a/lib/ssl/src/ssl_connection.hrl +++ b/lib/ssl/src/ssl_connection.hrl @@ -33,75 +33,145 @@ -include("ssl_cipher.hrl"). -include_lib("public_key/include/public_key.hrl"). +-record(static_env, { + role :: client | server, + transport_cb :: atom(), % callback module + protocol_cb :: tls_connection | dtls_connection, + data_tag :: atom(), % ex tcp. + close_tag :: atom(), % ex tcp_closed + error_tag :: atom(), % ex tcp_error + host :: string() | inet:ip_address(), + port :: integer(), + socket :: port() | tuple(), %% TODO: dtls socket + cert_db :: reference() | 'undefined', + session_cache :: db_handle(), + session_cache_cb :: atom(), + crl_db :: term(), + file_ref_db :: db_handle(), + cert_db_ref :: certdb_ref() | 'undefined', + tracker :: pid() | 'undefined' %% Tracker process for listen socket + }). + +-record(handshake_env, { + client_hello_version :: ssl_record:ssl_version() | 'undefined', + unprocessed_handshake_events = 0 :: integer(), + tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() + | 'undefined', + renegotiation :: undefined | {boolean(), From::term() | internal | peer} + }). + -record(state, { - role :: client | server, - user_application :: {Monitor::reference(), User::pid()}, - transport_cb :: atom(), % callback module - protocol_cb :: tls_connection | dtls_connection, - data_tag :: atom(), % ex tcp. - close_tag :: atom(), % ex tcp_closed - error_tag :: atom(), % ex tcp_error - host :: string() | inet:ip_address(), - port :: integer(), - socket :: port() | tuple(), %% TODO: dtls socket - sender :: pid() | undefined, - ssl_options :: #ssl_options{}, - socket_options :: #socket_options{}, - connection_states :: ssl_record:connection_states() | secret_printout(), - protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl - unprocessed_handshake_events = 0 :: integer(), - tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() - | 'undefined', - cert_db :: reference() | 'undefined', - session :: #session{} | secret_printout(), - session_cache :: db_handle(), - session_cache_cb :: atom(), - crl_db :: term(), - negotiated_version :: ssl_record:ssl_version() | 'undefined', - client_hello_version :: ssl_record:ssl_version() | 'undefined', - client_certificate_requested = false :: boolean(), - key_algorithm :: ssl_cipher_format:key_algo(), - hashsign_algorithm = {undefined, undefined}, - cert_hashsign_algorithm = {undefined, undefined}, - public_key_info :: ssl_handshake:public_key_info() | 'undefined', - private_key :: public_key:private_key() | secret_printout() | 'undefined', - diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), - diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), - psk_identity :: binary() | 'undefined', % server psk identity hint - srp_params :: #srp_user{} | secret_printout() | 'undefined', - srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', - premaster_secret :: binary() | secret_printout() | 'undefined', - file_ref_db :: db_handle(), - cert_db_ref :: certdb_ref() | 'undefined', - bytes_to_read :: undefined | integer(), %% bytes to read in passive mode - user_data_buffer :: undefined | binary() | secret_printout(), - erl_dist_data = #{} :: map(), - renegotiation :: undefined | {boolean(), From::term() | internal | peer}, - start_or_recv_from :: term(), - timer :: undefined | reference(), % start_or_recive_timer - %%send_queue :: queue:queue(), - hello, %%:: #client_hello{} | #server_hello{}, - terminated = false ::boolean(), - allow_renegotiate = true ::boolean(), - expecting_next_protocol_negotiation = false ::boolean(), - expecting_finished = false ::boolean(), - next_protocol = undefined :: undefined | binary(), - negotiated_protocol, - tracker :: pid() | 'undefined', %% Tracker process for listen socket - sni_hostname = undefined, - downgrade, - flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake - %% to when possible pack more than one TLS record into the - %% underlaying packet format. Introduced by DTLS - RFC 4347. - %% The mecahnism is also usefull in TLS although we do not - %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr - flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. - protocol_specific = #{} :: map(), - key_share - }). + static_env :: #static_env{}, + handshake_env :: #handshake_env{} | secret_printout(), + %% Change seldome + user_application :: {Monitor::reference(), User::pid()}, + ssl_options :: #ssl_options{}, + socket_options :: #socket_options{}, + session :: #session{} | secret_printout(), + allow_renegotiate = true ::boolean(), + terminated = false ::boolean() | closed, + negotiated_version :: ssl_record:ssl_version() | 'undefined', + bytes_to_read :: undefined | integer(), %% bytes to read in passive mode + downgrade, + + %% Changed often + connection_states :: ssl_record:connection_states() | secret_printout(), + protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hr + user_data_buffer :: undefined | binary() | secret_printout(), + + %% Used only in HS + + client_certificate_requested = false :: boolean(), + key_algorithm :: ssl_cipher_format:key_algo(), + hashsign_algorithm = {undefined, undefined}, + cert_hashsign_algorithm = {undefined, undefined}, + public_key_info :: ssl_handshake:public_key_info() | 'undefined', + private_key :: public_key:private_key() | secret_printout() | 'undefined', + diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), + diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), + psk_identity :: binary() | 'undefined', % server psk identity hint + srp_params :: #srp_user{} | secret_printout() | 'undefined', + srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', + premaster_secret :: binary() | secret_printout() | 'undefined', + start_or_recv_from :: term(), + timer :: undefined | reference(), % start_or_recive_timer + hello, %%:: #client_hello{} | #server_hello{}, + expecting_next_protocol_negotiation = false ::boolean(), + expecting_finished = false ::boolean(), + next_protocol = undefined :: undefined | binary(), + negotiated_protocol, + sni_hostname = undefined, + flight_buffer = [] :: list() | map(), %% Buffer of TLS/DTLS records, used during the TLS handshake + %% to when possible pack more than one TLS record into the + %% underlaying packet format. Introduced by DTLS - RFC 4347. + %% The mecahnism is also usefull in TLS although we do not + %% need to worry about packet loss in TLS. In DTLS we need to track DTLS handshake seqnr + flight_state = reliable, %% reliable | {retransmit, integer()}| {waiting, ref(), integer()} - last two is used in DTLS over udp. + erl_dist_handle = undefined :: erlang:dist_handle() | undefined, + protocol_specific = #{} :: map(), + key_share + }). + -define(DEFAULT_DIFFIE_HELLMAN_PARAMS, #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). -define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). + +%%---------------------------------------------------------------------- +%% TLS 1.3 +%%---------------------------------------------------------------------- + +%% TLS 1.3 uses the same state record with the following differences: +%% +%% state :: record() +%% +%% session_cache - not implemented +%% session_cache_cb - not implemented +%% crl_db - not implemented +%% client_hello_version - Bleichenbacher mitigation in TLS 1.2 +%% client_certificate_requested - Built into TLS 1.3 state machine +%% key_algorithm - not used +%% diffie_hellman_params - used in TLS 1.2 ECDH key exchange +%% diffie_hellman_keys - used in TLS 1.2 ECDH key exchange +%% psk_identity - not used +%% srp_params - not used, no srp extension in TLS 1.3 +%% srp_keys - not used, no srp extension in TLS 1.3 +%% premaster_secret - not used +%% renegotiation - TLS 1.3 forbids renegotiation +%% hello - used in user_hello, handshake continue +%% allow_renegotiate - TLS 1.3 forbids renegotiation +%% expecting_next_protocol_negotiation - ALPN replaced NPN, depricated in TLS 1.3 +%% expecting_finished - not implemented, used by abbreviated +%% next_protocol - ALPN replaced NPN, depricated in TLS 1.3 +%% +%% connection_state :: map() +%% +%% compression_state - not used +%% mac_secret - not used +%% sequence_number - not used +%% secure_renegotiation - not used, no renegotiation_info in TLS 1.3 +%% client_verify_data - not used, no renegotiation_info in TLS 1.3 +%% server_verify_data - not used, no renegotiation_info in TLS 1.3 +%% beast_mitigation - not used +%% +%% security_parameters :: map() +%% +%% cipher_type - TLS 1.3 uses only AEAD ciphers +%% iv_size - not used +%% key_size - not used +%% key_material_length - not used +%% expanded_key_material_length - used in SSL 3.0 +%% mac_algorithm - not used +%% prf_algorithm - not used +%% hash_size - not used +%% compression_algorithm - not used +%% master_secret - used for multiple secret types in TLS 1.3 +%% client_random - not used +%% server_random - not used +%% exportable - not used +%% +%% cipher_state :: record() +%% nonce - used for sequence_number + -endif. % -ifdef(ssl_connection). diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 417e5d9eb6..5e3c767c2c 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -61,7 +61,7 @@ -export([encode_handshake/2, encode_hello_extensions/1, encode_extensions/1, encode_extensions/2, encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1]). %% Decode --export([decode_handshake/3, decode_vector/1, decode_hello_extensions/3, decode_extensions/3, +-export([decode_handshake/3, decode_vector/1, decode_hello_extensions/4, decode_extensions/3, decode_server_key/3, decode_client_key/3, decode_suites/2 ]). @@ -639,7 +639,7 @@ encode_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Re ?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>); encode_extensions([#srp{username = UserName} | Rest], Acc) -> SRPLen = byte_size(UserName), - Len = SRPLen + 2, + Len = SRPLen + 1, encode_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), UserName/binary, Acc/binary>>); encode_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) -> @@ -680,9 +680,9 @@ encode_extensions([#sni{hostname = Hostname} | Rest], Acc) -> encode_extensions([#client_hello_versions{versions = Versions0} | Rest], Acc) -> Versions = encode_versions(Versions0), VerLen = byte_size(Versions), - Len = VerLen + 2, + Len = VerLen + 1, encode_extensions(Rest, <<?UINT16(?SUPPORTED_VERSIONS_EXT), - ?UINT16(Len), ?UINT16(VerLen), Versions/binary, Acc/binary>>); + ?UINT16(Len), ?BYTE(VerLen), Versions/binary, Acc/binary>>); encode_extensions([#server_hello_selected_version{selected_version = Version0} | Rest], Acc) -> Version = encode_versions([Version0]), Len = byte_size(Version), %% 2 @@ -745,8 +745,7 @@ decode_handshake(Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32 ?BYTE(SID_length), Session_ID:SID_length/binary, Cipher_suite:2/binary, ?BYTE(Comp_method), ?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> - - HelloExtensions = decode_hello_extensions(Extensions, Version, server_hello), + HelloExtensions = decode_hello_extensions(Extensions, Version, {Major, Minor}, server_hello), #server_hello{ server_version = {Major,Minor}, @@ -803,11 +802,12 @@ decode_vector(<<?UINT16(Len), Vector:Len/binary>>) -> Vector. %%-------------------------------------------------------------------- --spec decode_hello_extensions(binary(), ssl_record:ssl_version(), atom()) -> map(). +-spec decode_hello_extensions(binary(), ssl_record:ssl_version(), + ssl_record:ssl_version(), atom()) -> map(). %% %% Description: Decodes TLS hello extensions %%-------------------------------------------------------------------- -decode_hello_extensions(Extensions, Version, MessageType0) -> +decode_hello_extensions(Extensions, LocalVersion, LegacyVersion, MessageType0) -> %% Convert legacy atoms MessageType = case MessageType0 of @@ -815,6 +815,13 @@ decode_hello_extensions(Extensions, Version, MessageType0) -> server -> server_hello; T -> T end, + %% RFC 8446 - 4.2.1 + %% Servers MUST be prepared to receive ClientHellos that include this extension but + %% do not include 0x0304 in the list of versions. + %% Clients MUST check for this extension prior to processing the rest of the + %% ServerHello (although they will have to parse the ServerHello in order to read + %% the extension). + Version = process_supported_versions_extension(Extensions, LocalVersion, LegacyVersion), decode_extensions(Extensions, Version, MessageType, empty_extensions(Version, MessageType)). %%-------------------------------------------------------------------- @@ -1167,7 +1174,12 @@ kse_remove_private_key(#key_share_entry{ signature_algs_ext(undefined) -> undefined; -signature_algs_ext(SignatureSchemes) -> +signature_algs_ext(SignatureSchemes0) -> + %% The SSL option signature_algs contains both hash-sign algorithms (tuples) and + %% signature schemes (atoms) if TLS 1.3 is configured. + %% Filter out all hash-sign tuples when creating the signature_algs extension. + %% (TLS 1.3 specific record type) + SignatureSchemes = lists:filter(fun is_atom/1, SignatureSchemes0), #signature_algorithms{signature_scheme_list = SignatureSchemes}. signature_algs_cert(undefined) -> @@ -1191,7 +1203,8 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, Empty = empty_extensions(Version, server_hello), ServerHelloExtensions = Empty#{renegotiation_info => renegotiation_info(RecordCB, server, ConnectionStates, Renegotiation), - ec_point_formats => server_ecc_extension(Version, maps:get(ec_point_formats, Exts, undefined)) + ec_point_formats => server_ecc_extension(Version, + maps:get(ec_point_formats, Exts, undefined)) }, %% If we receive an ALPN extension and have ALPN configured for this connection, @@ -1199,13 +1212,9 @@ handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, ALPN = maps:get(alpn, Exts, undefined), if ALPN =/= undefined, ALPNPreferredProtocols =/= undefined -> - case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of - #alert{} = Alert -> - Alert; - Protocol -> - {Session, ConnectionStates, Protocol, - ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}} - end; + Protocol = handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)), + {Session, ConnectionStates, Protocol, + ServerHelloExtensions#{alpn => encode_alpn([Protocol], Renegotiation)}}; true -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), @@ -1219,7 +1228,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, #ssl_options{secure_renegotiate = SecureRenegotation, next_protocol_selector = NextProtoSelector}, ConnectionStates0, Renegotiation) -> - ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, maps:get(renegotiation_info, Exts, undefined), Random, + ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, + maps:get(renegotiation_info, Exts, undefined), Random, CipherSuite, undefined, Compression, ConnectionStates0, Renegotiation, SecureRenegotation), @@ -1234,12 +1244,8 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, {ConnectionStates, alpn, Protocol}; undefined -> NextProtocolNegotiation = maps:get(next_protocol_negotiation, Exts, undefined), - case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of - #alert{} = Alert -> - Alert; - Protocol -> - {ConnectionStates, npn, Protocol} - end; + Protocol = handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation), + {ConnectionStates, npn, Protocol}; {error, Reason} -> ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); [] -> @@ -2201,6 +2207,47 @@ dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) -> dec_server_key_signature(_, _, _) -> throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)). +%% Processes a ClientHello/ServerHello message and returns the version to be used +%% in the decoding functions. The following rules apply: +%% - IF supported_versions extension is absent: +%% RETURN the lowest of (LocalVersion and LegacyVersion) +%% - IF supported_versions estension is present: +%% RETURN the lowest of (LocalVersion and first element of supported versions) +process_supported_versions_extension(<<>>, LocalVersion, LegacyVersion) + when LegacyVersion =< LocalVersion -> + LegacyVersion; +process_supported_versions_extension(<<>>, LocalVersion, _LegacyVersion) -> + LocalVersion; +process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), + ExtData:Len/binary, _Rest/binary>>, + LocalVersion, _LegacyVersion) when Len > 2 -> + <<?BYTE(_),Versions0/binary>> = ExtData, + [Highest|_] = decode_versions(Versions0), + if Highest =< LocalVersion -> + Highest; + true -> + LocalVersion + end; +process_supported_versions_extension(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), + ?BYTE(Major),?BYTE(Minor), _Rest/binary>>, + LocalVersion, _LegacyVersion) when Len =:= 2 -> + SelectedVersion = {Major, Minor}, + if SelectedVersion =< LocalVersion -> + SelectedVersion; + true -> + LocalVersion + end; +process_supported_versions_extension(<<?UINT16(_), ?UINT16(Len), + _ExtData:Len/binary, Rest/binary>>, + LocalVersion, LegacyVersion) -> + process_supported_versions_extension(Rest, LocalVersion, LegacyVersion); +%% Tolerate protocol encoding errors and skip parsing the rest of the extension. +process_supported_versions_extension(_, LocalVersion, LegacyVersion) + when LegacyVersion =< LocalVersion -> + LegacyVersion; +process_supported_versions_extension(_, LocalVersion, _) -> + LocalVersion. + decode_extensions(<<>>, _Version, _MessageType, Acc) -> Acc; decode_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), @@ -2229,7 +2276,7 @@ decode_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), decode_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Version, MessageType, Acc) - when Len == SRPLen + 2 -> + when Len == SRPLen + 1 -> decode_extensions(Rest, Version, MessageType, Acc#{srp => #srp{username = SRP}}); decode_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), @@ -2327,7 +2374,7 @@ decode_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), decode_extensions(<<?UINT16(?SUPPORTED_VERSIONS_EXT), ?UINT16(Len), ExtData:Len/binary, Rest/binary>>, Version, MessageType, Acc) when Len > 2 -> - <<?UINT16(_),Versions/binary>> = ExtData, + <<?BYTE(_),Versions/binary>> = ExtData, decode_extensions(Rest, Version, MessageType, Acc#{client_hello_versions => #client_hello_versions{ @@ -2596,30 +2643,26 @@ filter_unavailable_ecc_suites(_, Suites) -> handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, ClientCipherSuites, Compression, ConnectionStates0, Renegotiation, SecureRenegotation) -> - case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, - ClientCipherSuites) of - {ok, ConnectionStates} -> - hello_pending_connection_states(RecordCB, Role, - Version, - NegotiatedCipherSuite, - Random, - Compression, - ConnectionStates); - #alert{} = Alert -> - throw(Alert) - end. + {ok, ConnectionStates} = handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, + ClientCipherSuites), + hello_pending_connection_states(RecordCB, Role, + Version, + NegotiatedCipherSuite, + Random, + Compression, + ConnectionStates). %% Receive protocols, choose one from the list, return it. handle_alpn_extension(_, {error, Reason}) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason)); handle_alpn_extension([], _) -> - ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL); + throw(?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL)); handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> - case lists:member(ServerProtocol, ClientProtocols) of - true -> ServerProtocol; - false -> handle_alpn_extension(Tail, ClientProtocols) - end. + case lists:member(ServerProtocol, ClientProtocols) of + true -> ServerProtocol; + false -> handle_alpn_extension(Tail, ClientProtocols) + end. handle_next_protocol(undefined, _NextProtocolSelector, _Renegotiating) -> @@ -2632,14 +2675,14 @@ handle_next_protocol(#next_protocol_negotiation{} = NextProtocols, true -> select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector); false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension) + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension)) end. handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-> case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of #alert{} = Alert -> - Alert; + throw(Alert); ProtocolsToAdvertise -> ProtocolsToAdvertise end. @@ -2894,14 +2937,14 @@ handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_co true -> {ok, ConnectionStates}; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation) + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation)) end; handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, ConnectionStates, true, _, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); false -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), Data = maps:get(client_verify_data, ConnectionState), @@ -2909,7 +2952,7 @@ handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_co true -> {ok, ConnectionStates}; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation) + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation)) end end; @@ -2919,7 +2962,7 @@ handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, S handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv})); false -> handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) end. @@ -2928,9 +2971,9 @@ handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> ConnectionState = ssl_record:current_connection_state(ConnectionStates, read), case {SecureRenegotation, maps:get(secure_renegotiation, ConnectionState)} of {_, true} -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure); + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure)); {true, false} -> - ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); + throw(?ALERT_REC(?FATAL, ?NO_RENEGOTIATION)); {false, false} -> {ok, ConnectionStates} end. diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 48798799f7..57b72366d3 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -61,6 +61,7 @@ -define(CDR_MAGIC, "GIOP"). -define(CDR_HDR_SIZE, 12). +-define(INTERNAL_ACTIVE_N, 100). -define(DEFAULT_TIMEOUT, 5000). -define(NO_DIST_POINT, "http://dummy/no_distribution_point"). @@ -136,10 +137,10 @@ %% Local policy for the server if it want's to reuse the session %% or not. Defaluts to allways returning true. %% fun(SessionId, PeerCert, Compression, CipherSuite) -> boolean() - reuse_session, + reuse_session :: fun() | binary() | undefined, %% Server side is a fun() %% If false sessions will never be reused, if true they %% will be reused if possible. - reuse_sessions :: boolean(), + reuse_sessions :: boolean() | save, %% Only client side can use value save renegotiate_at, secure_renegotiate, client_renegotiation, @@ -175,6 +176,8 @@ max_handshake_size :: integer(), handshake, customize_hostname_check + %% , + %% save_session :: boolean() }). -record(socket_options, diff --git a/lib/ssl/src/ssl_logger.erl b/lib/ssl/src/ssl_logger.erl index 35c8dcfd48..ce8225bf72 100644 --- a/lib/ssl/src/ssl_logger.erl +++ b/lib/ssl/src/ssl_logger.erl @@ -32,6 +32,7 @@ -define(rec_info(T,R),lists:zip(record_info(fields,T),tl(tuple_to_list(R)))). -include("tls_record.hrl"). +-include("ssl_cipher.hrl"). -include("ssl_internal.hrl"). -include("tls_handshake.hrl"). -include_lib("kernel/include/logger.hrl"). @@ -87,20 +88,32 @@ format_handshake(Direction, BinMsg) -> parse_handshake(Direction, #client_hello{ - client_version = Version + client_version = Version0, + cipher_suites = CipherSuites0, + extensions = Extensions } = ClientHello) -> + Version = get_client_version(Version0, Extensions), Header = io_lib:format("~s ~s Handshake, ClientHello", [header_prefix(Direction), version(Version)]), - Message = io_lib:format("~p", [?rec_info(client_hello, ClientHello)]), + CipherSuites = parse_cipher_suites(CipherSuites0), + Message = io_lib:format("~p", + [?rec_info(client_hello, + ClientHello#client_hello{cipher_suites = CipherSuites})]), {Header, Message}; parse_handshake(Direction, #server_hello{ - server_version = Version + server_version = Version0, + cipher_suite = CipherSuite0, + extensions = Extensions } = ServerHello) -> + Version = get_server_version(Version0, Extensions), Header = io_lib:format("~s ~s Handshake, ServerHello", [header_prefix(Direction), version(Version)]), - Message = io_lib:format("~p", [?rec_info(server_hello, ServerHello)]), + CipherSuite = format_cipher(CipherSuite0), + Message = io_lib:format("~p", + [?rec_info(server_hello, + ServerHello#server_hello{cipher_suite = CipherSuite})]), {Header, Message}; parse_handshake(Direction, #certificate{} = Certificate) -> Header = io_lib:format("~s Handshake, Certificate", @@ -148,7 +161,34 @@ parse_handshake(Direction, #hello_request{} = HelloRequest) -> Message = io_lib:format("~p", [?rec_info(hello_request, HelloRequest)]), {Header, Message}. +parse_cipher_suites([_|_] = Ciphers) -> + [format_cipher(C) || C <- Ciphers]. + +format_cipher(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV) -> + 'TLS_EMPTY_RENEGOTIATION_INFO_SCSV'; +format_cipher(C0) -> + list_to_atom(ssl_cipher_format:openssl_suite_name(C0)). + +get_client_version(Version, Extensions) -> + CHVersions = maps:get(client_hello_versions, Extensions, undefined), + case CHVersions of + #client_hello_versions{versions = [Highest|_]} -> + Highest; + undefined -> + Version + end. + +get_server_version(Version, Extensions) -> + SHVersion = maps:get(server_hello_selected_version, Extensions, undefined), + case SHVersion of + #server_hello_selected_version{selected_version = SelectedVersion} -> + SelectedVersion; + undefined -> + Version + end. +version({3,4}) -> + "TLS 1.3"; version({3,3}) -> "TLS 1.2"; version({3,2}) -> diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index c938772bc1..b1f080b0fe 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -30,7 +30,7 @@ connection_init/3, cache_pem_file/2, lookup_trusted_cert/4, new_session_id/1, clean_cert_db/2, - register_session/2, register_session/3, invalidate_session/2, + register_session/2, register_session/4, invalidate_session/2, insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2, invalidate_session/3, name/1]). @@ -170,9 +170,11 @@ clean_cert_db(Ref, File) -> %% %% Description: Make the session available for reuse. %%-------------------------------------------------------------------- --spec register_session(host(), inet:port_number(), #session{}) -> ok. -register_session(Host, Port, Session) -> - cast({register_session, Host, Port, Session}). +-spec register_session(host(), inet:port_number(), #session{}, unique | true) -> ok. +register_session(Host, Port, Session, true) -> + call({register_session, Host, Port, Session}); +register_session(Host, Port, Session, unique = Save) -> + cast({register_session, Host, Port, Session, Save}). -spec register_session(inet:port_number(), #session{}) -> ok. register_session(Port, Session) -> @@ -301,7 +303,10 @@ handle_call({{new_session_id, Port}, _}, _, #state{session_cache_cb = CacheCb, session_cache_server = Cache} = State) -> Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), - {reply, Id, State}. + {reply, Id, State}; +handle_call({{register_session, Host, Port, Session},_}, _, State0) -> + State = client_register_session(Host, Port, Session, State0), + {reply, ok, State}. %%-------------------------------------------------------------------- -spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. @@ -311,8 +316,12 @@ handle_call({{new_session_id, Port}, _}, %% %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast({register_session, Host, Port, Session}, State0) -> - State = ssl_client_register_session(Host, Port, Session, State0), +handle_cast({register_session, Host, Port, Session, unique}, State0) -> + State = client_register_unique_session(Host, Port, Session, State0), + {noreply, State}; + +handle_cast({register_session, Host, Port, Session, true}, State0) -> + State = client_register_session(Host, Port, Session, State0), {noreply, State}; handle_cast({register_session, Port, Session}, State0) -> @@ -540,10 +549,10 @@ clean_cert_db(Ref, CertDb, RefDb, FileMapDb, File) -> ok end. -ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache, - session_cache_cb = CacheCb, - session_cache_client_max = Max, - session_client_invalidator = Pid0} = State) -> +client_register_unique_session(Host, Port, Session, #state{session_cache_client = Cache, + session_cache_cb = CacheCb, + session_cache_client_max = Max, + session_client_invalidator = Pid0} = State) -> TimeStamp = erlang:monotonic_time(), NewSession = Session#session{time_stamp = TimeStamp}, @@ -557,6 +566,17 @@ ssl_client_register_session(Host, Port, Session, #state{session_cache_client = C register_unique_session(Sessions, NewSession, {Host, Port}, State) end. +client_register_session(Host, Port, Session, #state{session_cache_client = Cache, + session_cache_cb = CacheCb, + session_cache_client_max = Max, + session_client_invalidator = Pid0} = State) -> + TimeStamp = erlang:monotonic_time(), + NewSession = Session#session{time_stamp = TimeStamp}, + Pid = do_register_session({{Host, Port}, + NewSession#session.session_id}, + NewSession, Max, Pid0, Cache, CacheCb), + State#state{session_client_invalidator = Pid}. + server_register_session(Port, Session, #state{session_cache_server_max = Max, session_cache_server = Cache, session_cache_cb = CacheCb, diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index ddc83821b4..499ba108f2 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -39,7 +39,8 @@ set_renegotiation_flag/2, set_client_verify_data/3, set_server_verify_data/3, - empty_connection_state/2, initial_connection_state/2, record_protocol_role/1]). + empty_connection_state/2, initial_connection_state/2, record_protocol_role/1, + step_encryption_state/1]). %% Compression -export([compress/3, uncompress/3, compressions/0]). @@ -118,6 +119,20 @@ activate_pending_connection_state(#{current_write := Current, }. %%-------------------------------------------------------------------- +-spec step_encryption_state(connection_states()) -> connection_states(). +%% +%% Description: Activates the next encyrption state (e.g. handshake +%% encryption). +%%-------------------------------------------------------------------- +step_encryption_state(#{pending_read := PendingRead, + pending_write := PendingWrite} = States) -> + NewRead = PendingRead#{sequence_number => 0}, + NewWrite = PendingWrite#{sequence_number => 0}, + States#{current_read => NewRead, + current_write => NewWrite}. + + +%%-------------------------------------------------------------------- -spec set_security_params(#security_parameters{}, #security_parameters{}, connection_states()) -> connection_states(). %% diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index c9607489e9..a9759c9b43 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -53,6 +53,13 @@ is_new(_ClientSuggestion, _ServerDecision) -> %% Description: Should be called by the client side to get an id %% for the client hello message. %%-------------------------------------------------------------------- +client_id({Host, Port, #ssl_options{reuse_session = SessionId}}, Cache, CacheCb, _) when is_binary(SessionId)-> + case CacheCb:lookup(Cache, {{Host, Port}, SessionId}) of + undefined -> + <<>>; + #session{} -> + SessionId + end; client_id(ClientInfo, Cache, CacheCb, OwnCert) -> case select_session(ClientInfo, Cache, CacheCb, OwnCert) of no_session -> @@ -91,7 +98,8 @@ server_id(Port, SuggestedId, Options, Cert, Cache, CacheCb) -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -select_session({_, _, #ssl_options{reuse_sessions=false}}, _Cache, _CacheCb, _OwnCert) -> +select_session({_, _, #ssl_options{reuse_sessions = Reuse}}, _Cache, _CacheCb, _OwnCert) when Reuse =/= true -> + %% If reuse_sessions == true | save a new session should be created no_session; select_session({HostIP, Port, SslOpts}, Cache, CacheCb, OwnCert) -> Sessions = CacheCb:select_session(Cache, {HostIP, Port}), @@ -132,7 +140,7 @@ is_resumable(SuggestedSessionId, Port, #ssl_options{reuse_session = ReuseFun} = false -> {false, undefined} end; undefined -> - {false, undefined} + {false, undefined} end. resumable(new) -> diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 5e6ba652f0..26f9fc99d3 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -17,7 +17,6 @@ %% %% %CopyrightEnd% %% - %% %%---------------------------------------------------------------------- %% Purpose: Handles an ssl connection, e.i. both the setup @@ -47,8 +46,8 @@ -export([start_fsm/8, start_link/8, init/1, pids/1]). %% State transition handling --export([next_record/1, next_event/3, next_event/4, - handle_common_event/4]). +-export([next_event/3, next_event/4, + handle_protocol_record/3]). %% Handshake handling -export([renegotiation/2, renegotiate/2, send_handshake/2, @@ -62,7 +61,7 @@ encode_alert/3, close/5, protocol_name/0]). %% Data handling --export([encode_data/3, passive_receive/2, next_record_if_active/1, +-export([encode_data/3, next_record/1, send/3, socket/5, setopts/3, getopts/3]). %% gen_statem state functions @@ -162,49 +161,51 @@ pids(#state{protocol_specific = #{sender := Sender}}) -> %%==================================================================== %% State transition handling %%==================================================================== -next_record(#state{unprocessed_handshake_events = N} = State) when N > 0 -> - {no_record, State#state{unprocessed_handshake_events = N-1}}; - +next_record(#state{handshake_env = + #handshake_env{unprocessed_handshake_events = N} = HsEnv} + = State) when N > 0 -> + {no_record, State#state{handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events = N-1}}}; next_record(#state{protocol_buffers = - #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]} - = Buffers, - connection_states = ConnStates0, + #protocol_buffers{tls_packets = [], tls_cipher_texts = [#ssl_tls{type = Type}| _] = CipherTexts0} + = Buffers, + connection_states = ConnectionStates0, negotiated_version = Version, - ssl_options = #ssl_options{padding_check = Check}} = State) -> - - case tls_record:decode_cipher_text(Version, CT, ConnStates0, Check) of - {Plain, ConnStates} -> - {Plain, State#state{protocol_buffers = - Buffers#protocol_buffers{tls_cipher_texts = Rest}, - connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end; + ssl_options = #ssl_options{padding_check = Check}} = State) -> + case decode_cipher_texts(Version, Type, CipherTexts0, ConnectionStates0, Check, <<>>) of + {#ssl_tls{} = Record, ConnectionStates, CipherTexts} -> + {Record, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, + connection_states = ConnectionStates}}; + {#alert{} = Alert, ConnectionStates, CipherTexts} -> + {Alert, State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts}, + connection_states = ConnectionStates}} + end; next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, - socket = Socket, - close_tag = CloseTag, - transport_cb = Transport} = State) -> - case tls_socket:setopts(Transport, Socket, [{active,once}]) of - ok -> - {no_record, State}; - _ -> - self() ! {CloseTag, Socket}, - {no_record, State} - end; + protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec, + static_env = #static_env{socket = Socket, + close_tag = CloseTag, + transport_cb = Transport} + } = State) -> + case tls_socket:setopts(Transport, Socket, [{active, N}]) of + ok -> + {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}}; + _ -> + self() ! {CloseTag, Socket}, + {no_record, State} + end; next_record(State) -> {no_record, State}. next_event(StateName, Record, State) -> next_event(StateName, Record, State, []). - -next_event(connection = StateName, no_record, State0, Actions) -> - case next_record_if_active(State0) of - {no_record, State} -> - ssl_connection:hibernate_after(StateName, State, Actions); - {#ssl_tls{} = Record, State} -> - {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; - {#alert{} = Alert, State} -> - {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} +next_event(StateName, no_record, State0, Actions) -> + case next_record(State0) of + {no_record, State} -> + {next_state, StateName, State, Actions}; + {#ssl_tls{} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]}; + {#alert{} = Alert, State} -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end; next_event(StateName, Record, State, Actions) -> case Record of @@ -216,11 +217,34 @@ next_event(StateName, Record, State, Actions) -> {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end. -handle_common_event(internal, #alert{} = Alert, StateName, - #state{negotiated_version = Version} = State) -> - ssl_connection:handle_own_alert(Alert, Version, StateName, State); +decode_cipher_texts(_, Type, [] = CipherTexts, ConnectionStates, _, Acc) -> + {#ssl_tls{type = Type, fragment = Acc}, ConnectionStates, CipherTexts}; +decode_cipher_texts(Version, Type, + [#ssl_tls{type = Type} = CT | CipherTexts], ConnectionStates0, Check, Acc) -> + case tls_record:decode_cipher_text(Version, CT, ConnectionStates0, Check) of + {#ssl_tls{type = ?APPLICATION_DATA, fragment = Plain}, ConnectionStates} -> + decode_cipher_texts(Version, Type, CipherTexts, + ConnectionStates, Check, <<Acc/binary, Plain/binary>>); + {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates} -> + {#ssl_tls{type = Type, fragment = Plain}, ConnectionStates, CipherTexts}; + #alert{} = Alert -> + {Alert, ConnectionStates0, CipherTexts} + end; +decode_cipher_texts(_, Type, CipherTexts, ConnectionStates, _, Acc) -> + {#ssl_tls{type = Type, fragment = Acc}, ConnectionStates, CipherTexts}. + +%%% TLS record protocol level application data messages + +handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State0) -> + case ssl_connection:read_application_data(Data, State0) of + {stop, _, _} = Stop-> + Stop; + {Record, State1} -> + {next_state, StateName, State, Actions} = next_event(StateName, Record, State1), + ssl_connection:hibernate_after(StateName, State, Actions) + end; %%% TLS record protocol level handshake messages -handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, +handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data}, StateName, #state{protocol_buffers = #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, negotiated_version = Version, @@ -228,36 +252,36 @@ handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, try EffectiveVersion = effective_version(Version, Options), {Packets, Buf} = tls_handshake:get_tls_handshake(EffectiveVersion,Data,Buf0, Options), - State1 = + State = State0#state{protocol_buffers = Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, case Packets of [] -> assert_buffer_sanity(Buf, Options), - {Record, State} = next_record(State1), - next_event(StateName, Record, State); + next_event(StateName, no_record, State); _ -> Events = tls_handshake_events(Packets), case StateName of connection -> - ssl_connection:hibernate_after(StateName, State1, Events); + ssl_connection:hibernate_after(StateName, State, Events); _ -> + HsEnv = State#state.handshake_env, {next_state, StateName, - State1#state{unprocessed_handshake_events = unprocessed_events(Events)}, Events} + State#state{protocol_buffers = Buffers, + handshake_env = + HsEnv#handshake_env{unprocessed_handshake_events + = unprocessed_events(Events)}}, Events} end end catch throw:#alert{} = Alert -> 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) -> - {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]}; %%% TLS record protocol level change cipher messages -handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> +handle_protocol_record(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; %%% TLS record protocol level Alert messages -handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, - #state{negotiated_version = Version} = State) -> +handle_protocol_record(#ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{negotiated_version = Version} = State) -> try decode_alerts(EncAlerts) of Alerts = [_|_] -> handle_alerts(Alerts, {next_state, StateName, State}); @@ -273,23 +297,25 @@ handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, Sta end; %% Ignore unknown TLS record level protocol messages -handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> - {next_state, StateName, State}. +handle_protocol_record(#ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State, []}. %%==================================================================== %% Handshake handling %%==================================================================== renegotiation(Pid, WriteState) -> gen_statem:call(Pid, {user_renegotiate, WriteState}). -renegotiate(#state{role = client} = State, Actions) -> +renegotiate(#state{static_env = #static_env{role = client}, + handshake_env = HsEnv} = 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}, + {next_state, connection, State#state{handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, [{next_event, internal, #hello_request{}} | Actions]}; -renegotiate(#state{role = server, - socket = Socket, - transport_cb = Transport, +renegotiate(#state{static_env = #static_env{role = server, + socket = Socket, + transport_cb = Transport}, + handshake_env = HsEnv, negotiated_version = Version, connection_states = ConnectionStates0} = State0, Actions) -> HelloRequest = ssl_handshake:hello_request(), @@ -298,17 +324,16 @@ renegotiate(#state{role = server, {BinMsg, ConnectionStates} = tls_record:encode_handshake(Frag, Version, ConnectionStates0), send(Transport, Socket, BinMsg), - State1 = State0#state{connection_states = + State = State0#state{connection_states = ConnectionStates, - tls_handshake_history = Hs0}, - {Record, State} = next_record(State1), - next_event(hello, Record, State, Actions). + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hs0}}, + next_event(hello, no_record, State, Actions). send_handshake(Handshake, State) -> send_handshake_flight(queue_handshake(Handshake, State)). queue_handshake(Handshake, #state{negotiated_version = Version, - tls_handshake_history = Hist0, + handshake_env = #handshake_env{tls_handshake_history = Hist0} = HsEnv, flight_buffer = Flight0, connection_states = ConnectionStates0, ssl_options = SslOpts} = State0) -> @@ -324,11 +349,11 @@ queue_handshake(Handshake, #state{negotiated_version = Version, ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), State0#state{connection_states = ConnectionStates, - tls_handshake_history = Hist, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Hist}, flight_buffer = Flight0 ++ [BinHandshake]}. -send_handshake_flight(#state{socket = Socket, - transport_cb = Transport, +send_handshake_flight(#state{static_env = #static_env{socket = Socket, + transport_cb = Transport}, flight_buffer = Flight} = State0) -> send(Transport, Socket, Flight), {State0#state{flight_buffer = []}, []}. @@ -352,14 +377,14 @@ reinit(#state{protocol_specific = #{sender := Sender}, tls_sender:update_connection_state(Sender, Write, Version), reinit_handshake_data(State). -reinit_handshake_data(State) -> +reinit_handshake_data(#state{handshake_env = HsEnv} =State) -> %% premaster_secret, public_key_info and tls_handshake_info %% are only needed during the handshake phase. %% To reduce memory foot print of a connection reinitialize them. State#state{ premaster_secret = undefined, public_key_info = undefined, - tls_handshake_history = ssl_handshake:init_handshake_history() + handshake_env = HsEnv#handshake_env{tls_handshake_history = ssl_handshake:init_handshake_history()} }. select_sni_extension(#client_hello{extensions = #{sni := SNI}}) -> @@ -383,12 +408,13 @@ empty_connection_state(ConnectionEnd, BeastMitigation) -> encode_alert(#alert{} = Alert, Version, ConnectionStates) -> tls_record:encode_alert_record(Alert, Version, ConnectionStates). -send_alert(Alert, #state{negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0, - ssl_options = SslOpts} = StateData0) -> - {BinMsg, ConnectionStates} = encode_alert(Alert, Version, ConnectionStates0), +send_alert(Alert, #state{negotiated_version = Version, + static_env = #static_env{socket = Socket, + transport_cb = Transport}, + ssl_options = SslOpts, + connection_states = ConnectionStates0} = StateData0) -> + {BinMsg, ConnectionStates} = + encode_alert(Alert, Version, ConnectionStates0), send(Transport, Socket, BinMsg), Report = #{direction => outbound, protocol => 'tls_record', @@ -408,13 +434,11 @@ send_alert_in_connection(#alert{description = ?CLOSE_NOTIFY} = Alert, State) -> send_alert_in_connection(Alert, #state{protocol_specific = #{sender := Sender}}) -> tls_sender:send_alert(Sender, Alert). -send_sync_alert(Alert, #state{protocol_specific = #{sender := Sender}}= State) -> - tls_sender:send_and_ack_alert(Sender, Alert), - receive - {Sender, ack_alert} -> - ok - after ?DEFAULT_TIMEOUT -> - %% Sender is blocked terminate anyway +send_sync_alert( + Alert, #state{protocol_specific = #{sender := Sender}} = State) -> + try tls_sender:send_and_ack_alert(Sender, Alert) + catch + _:_ -> throw({stop, {shutdown, own_alert}, State}) end. @@ -452,23 +476,6 @@ protocol_name() -> encode_data(Data, Version, ConnectionStates0)-> tls_record:encode_data(Data, Version, ConnectionStates0). -passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> - case Buffer of - <<>> -> - {Record, State} = next_record(State0), - next_event(StateName, Record, State); - _ -> - {Record, State} = ssl_connection:read_application_data(<<>>, State0), - next_event(StateName, Record, State) - end. - -next_record_if_active(State = - #state{socket_options = - #socket_options{active = false}}) -> - {no_record ,State}; -next_record_if_active(State) -> - next_record(State). - send(Transport, Socket, Data) -> tls_socket:send(Transport, Socket, Data). @@ -491,14 +498,17 @@ getopts(Transport, Socket, Tag) -> %%-------------------------------------------------------------------- init({call, From}, {start, Timeout}, - #state{host = Host, port = Port, role = client, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + transport_cb = Transport, + socket = Socket, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, ssl_options = SslOpts, session = #session{own_certificate = Cert} = Session0, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb + connection_states = ConnectionStates0 } = State0) -> KeyShare = maybe_generate_client_shares(SslOpts), Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), @@ -518,16 +528,16 @@ init({call, From}, {start, Timeout}, message => Hello}, ssl_logger:debug(SslOpts#ssl_options.log_level, HelloMsg, #{domain => [otp,ssl,handshake]}), ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - State1 = State0#state{connection_states = ConnectionStates, + State = State0#state{connection_states = ConnectionStates, negotiated_version = HelloVersion, %% Requested version - session = - Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake, - start_or_recv_from = From, - timer = Timer, - key_share = KeyShare}, - {Record, State} = next_record(State1), - next_event(hello, Record, State); + session = + Session0#session{session_id = Hello#client_hello.session_id}, + handshake_env = HsEnv#handshake_env{tls_handshake_history = Handshake}, + start_or_recv_from = From, + timer = Timer, + key_share = KeyShare}, + next_event(hello, no_record, State); + init(Type, Event, State) -> gen_handshake(?FUNCTION_NAME, Type, Event, State). @@ -538,8 +548,9 @@ init(Type, Event, State) -> %%-------------------------------------------------------------------- error({call, From}, {start, _Timeout}, #state{protocol_specific = #{error := Error}} = State) -> - ssl_connection:stop_and_reply( - normal, {reply, From, {error, Error}}, State); + {stop_and_reply, {shutdown, normal}, + [{reply, From, {error, Error}}], State}; + error({call, _} = Call, Msg, State) -> gen_handshake(?FUNCTION_NAME, Call, Msg, State); error(_, _, _) -> @@ -565,13 +576,16 @@ hello(internal, #server_hello{extensions = Extensions} = Hello, [{reply, From, {ok, Extensions}}]}; hello(internal, #client_hello{client_version = ClientVersion} = Hello, #state{connection_states = ConnectionStates0, - port = Port, session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb, + static_env = #static_env{ + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}} = HsEnv, + session = #session{own_certificate = Cert} = Session0, negotiated_protocol = CurrentProtocol, key_algorithm = KeyExAlg, ssl_options = SslOpts} = State) -> + case choose_tls_version(SslOpts, Hello) of 'tls_v1.3' -> %% Continue in TLS 1.3 'start' state @@ -598,7 +612,8 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, State#state{connection_states = ConnectionStates, negotiated_version = Version, hashsign_algorithm = HashSign, - client_hello_version = ClientVersion, + handshake_env = HsEnv#handshake_env{client_hello_version = + ClientVersion}, session = Session, negotiated_protocol = Protocol}) end @@ -606,8 +621,8 @@ hello(internal, #client_hello{client_version = ClientVersion} = Hello, hello(internal, #server_hello{} = Hello, #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, - role = client, - renegotiation = {Renegotiation, _}, + static_env = #static_env{role = client}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> @@ -663,46 +678,96 @@ connection({call, From}, {user_renegotiate, WriteState}, #state{connection_states = ConnectionStates} = State) -> {next_state, ?FUNCTION_NAME, State#state{connection_states = ConnectionStates#{current_write => WriteState}}, [{next_event,{call, From}, renegotiate}]}; +connection({call, From}, + {close, {Pid, _Timeout}}, + #state{terminated = closed} = State) -> + {next_state, downgrade, State#state{terminated = true, downgrade = {Pid, From}}, + [{next_event, internal, ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY)}]}; +connection({call, From}, + {close,{Pid, Timeout}}, + #state{connection_states = ConnectionStates, + protocol_specific = #{sender := Sender} + } = State0) -> + case tls_sender:downgrade(Sender, Timeout) of + {ok, Write} -> + %% User downgrades connection + %% When downgrading an TLS connection to a transport connection + %% we must recive the close alert from the peer before releasing the + %% transport socket. + State = send_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + State0#state{connection_states = + ConnectionStates#{current_write => Write}}), + {next_state, downgrade, State#state{downgrade = {Pid, From}, + terminated = true}, [{timeout, Timeout, downgrade}]}; + {error, timeout} -> + {stop_and_reply, {shutdown, downgrade_fail}, [{reply, From, {error, timeout}}]} + end; +connection(internal, #hello_request{}, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, peer}}, + session = #session{own_certificate = Cert} = Session0, + ssl_options = SslOpts, + protocol_specific = #{sender := Pid}, + connection_states = ConnectionStates} = State0) -> + try tls_sender:peer_renegotiate(Pid) of + {ok, Write} -> + Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, + Cache, CacheCb, Renegotiation, Cert, undefined), + {State, Actions} = send_handshake(Hello, State0#state{connection_states = ConnectionStates#{current_write => Write}}), + next_event(hello, no_record, State#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}, Actions) + catch + _:_ -> + {stop, {shutdown, sender_blocked}, State0} + end; connection(internal, #hello_request{}, - #state{role = client, - renegotiation = {Renegotiation, _}, - host = Host, port = Port, + #state{static_env = #static_env{role = client, + host = Host, + port = Port, + session_cache = Cache, + session_cache_cb = CacheCb}, + handshake_env = #handshake_env{renegotiation = {Renegotiation, _}}, session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - ssl_options = SslOpts, + ssl_options = SslOpts, connection_states = ConnectionStates} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates, SslOpts, Cache, CacheCb, Renegotiation, Cert, undefined), - {State1, Actions} = send_handshake(Hello, State0), - {Record, State} = - next_record( - State1#state{session = Session0#session{session_id - = Hello#client_hello.session_id}}), - next_event(hello, Record, State, Actions); + + {State, Actions} = send_handshake(Hello, State0), + next_event(hello, no_record, State#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}, Actions); connection(internal, #client_hello{} = Hello, - #state{role = server, allow_renegotiate = true, connection_states = CS, - %%protocol_cb = Connection, + #state{static_env = #static_env{role = server}, + handshake_env = HsEnv, + allow_renegotiate = true, + connection_states = CS, protocol_specific = #{sender := Sender} - } = State0) -> + } = State) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - {Record, State} = next_record(State0#state{allow_renegotiate = false, - renegotiation = {true, peer}}), {ok, Write} = tls_sender:renegotiate(Sender), - next_event(hello, Record, State#state{connection_states = CS#{current_write => Write}}, + next_event(hello, no_record, State#state{connection_states = CS#{current_write => Write}, + allow_renegotiate = false, + handshake_env = HsEnv#handshake_env{renegotiation = {true, peer}} + }, [{next_event, internal, Hello}]); connection(internal, #client_hello{}, - #state{role = server, allow_renegotiate = false, - protocol_cb = Connection} = State0) -> + #state{static_env = #static_env{role = server, + protocol_cb = Connection}, + allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), send_alert_in_connection(Alert, State0), - State1 = Connection:reinit_handshake_data(State0), - {Record, State} = next_record(State1), - next_event(?FUNCTION_NAME, Record, State); + State = Connection:reinit_handshake_data(State0), + next_event(?FUNCTION_NAME, no_record, State); + connection(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -710,6 +775,22 @@ connection(Type, Event, State) -> -spec downgrade(gen_statem:event_type(), term(), #state{}) -> gen_statem:state_function_result(). %%-------------------------------------------------------------------- +downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, + #state{static_env = #static_env{transport_cb = Transport, + socket = Socket}, + downgrade = {Pid, From}} = State) -> + tls_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), + Transport:controlling_process(Socket, Pid), + {stop_and_reply, {shutdown, downgrade},[{reply, From, {ok, Socket}}], State}; +downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State) -> + {stop_and_reply, {shutdown, normal},[{reply, From, {error, timeout}}], State}; +downgrade(info, {CloseTag, Socket}, + #state{static_env = #static_env{socket = Socket, + close_tag = CloseTag}, downgrade = {_, From}} = + State) -> + {stop_and_reply, {shutdown, normal},[{reply, From, {error, CloseTag}}], State}; +downgrade(info, Info, State) -> + handle_info(Info, ?FUNCTION_NAME, State); downgrade(Type, Event, State) -> ssl_connection:?FUNCTION_NAME(Type, Event, State, ?MODULE). @@ -830,6 +911,12 @@ wait_sh(Type, Event, State) -> callback_mode() -> state_functions. +terminate({shutdown, sender_died, Reason}, _StateName, + #state{static_env = #static_env{socket = Socket, + transport_cb = Transport}} + = State) -> + ssl_connection:handle_trusted_certs_db(State), + close(Reason, Socket, Transport, undefined, undefined); terminate(Reason, StateName, State) -> catch ssl_connection:terminate(Reason, StateName, State), ensure_sender_terminate(Reason, State). @@ -848,54 +935,62 @@ initial_state(Role, Sender, Host, Port, Socket, {SSLOptions, SocketOptions, Trac #ssl_options{beast_mitigation = BeastMitigation, erl_dist = IsErlDist} = SSLOptions, ConnectionStates = tls_record:init_connection_states(Role, BeastMitigation), - ErlDistData = erl_dist_data(IsErlDist), SessionCacheCb = case application:get_env(ssl, session_cb) of {ok, Cb} when is_atom(Cb) -> Cb; _ -> ssl_session_cache end, - + InternalActiveN = case application:get_env(ssl, internal_active_n) of + {ok, N} when is_integer(N) andalso (not IsErlDist) -> + N; + _ -> + ?INTERNAL_ACTIVE_N + end, UserMonitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, - ssl_options = SSLOptions, - session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, - erl_dist_data = ErlDistData, - connection_states = ConnectionStates, - protocol_buffers = #protocol_buffers{}, - user_application = {UserMonitor, User}, - user_data_buffer = <<>>, - session_cache_cb = SessionCacheCb, - renegotiation = {false, first}, - allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, - start_or_recv_from = undefined, - protocol_cb = ?MODULE, - tracker = Tracker, - flight_buffer = [], - protocol_specific = #{sender => Sender} - }. - -erl_dist_data(true) -> - #{dist_handle => undefined, - dist_buffer => <<>>}; -erl_dist_data(false) -> - #{}. - -initialize_tls_sender(#state{role = Role, - socket = Socket, + InitStatEnv = #static_env{ + role = Role, + transport_cb = CbModule, + protocol_cb = ?MODULE, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + host = Host, + port = Port, + socket = Socket, + session_cache_cb = SessionCacheCb, + tracker = Tracker + }, + #state{ + static_env = InitStatEnv, + handshake_env = #handshake_env{ + tls_handshake_history = ssl_handshake:init_handshake_history(), + renegotiation = {false, first} + }, + socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = new}, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_application = {UserMonitor, User}, + user_data_buffer = <<>>, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + start_or_recv_from = undefined, + flight_buffer = [], + protocol_specific = #{sender => Sender, + active_n => InternalActiveN, + active_n_toggle => true + } + }. + +initialize_tls_sender(#state{static_env = #static_env{ + role = Role, + transport_cb = Transport, + protocol_cb = Connection, + socket = Socket, + tracker = Tracker + }, socket_options = SockOpts, - tracker = Tracker, - protocol_cb = Connection, - transport_cb = Transport, negotiated_version = Version, ssl_options = #ssl_options{renegotiate_at = RenegotiateAt, log_level = LogLevel}, @@ -929,18 +1024,14 @@ next_tls_record(Data, StateName, #state{protocol_buffers = handle_record_alert(Alert, State0) end. - %% TLS 1.3 Client/Server %% - Ignore TLSPlaintext.legacy_record_version %% - Verify that TLSCiphertext.legacy_record_version is set to 0x0303 for all records %% other than an initial ClientHello, where it MAY also be 0x0301. +acceptable_record_versions(StateName, #state{negotiated_version = Version}) when StateName =/= hello-> + Version; acceptable_record_versions(hello, _) -> - [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_TLS_RECORD_VERSIONS]; -acceptable_record_versions(_, #state{negotiated_version = {Major, Minor}}) - when Major > 3; Major =:= 3, Minor >= 4 -> - [{3, 3}]; -acceptable_record_versions(_, #state{negotiated_version = Version}) -> - [Version]. + [tls_record:protocol_version(Vsn) || Vsn <- ?ALL_AVAILABLE_VERSIONS]. handle_record_alert(Alert, _) -> Alert. @@ -952,19 +1043,26 @@ tls_handshake_events(Packets) -> %% raw data from socket, upack records handle_info({Protocol, _, Data}, StateName, - #state{data_tag = Protocol} = State0) -> + #state{static_env = #static_env{data_tag = Protocol}} = State0) -> case next_tls_record(Data, StateName, State0) of {Record, State} -> next_event(StateName, Record, State); #alert{} = Alert -> ssl_connection:handle_normal_shutdown(Alert, StateName, State0), - ssl_connection:stop({shutdown, own_alert}, State0) + {stop, {shutdown, own_alert}, State0} end; +handle_info({tcp_passive, Socket}, StateName, + #state{static_env = #static_env{socket = Socket}, + protocol_specific = PS + } = State) -> + next_event(StateName, no_record, + State#state{protocol_specific = PS#{active_n_toggle => true}}); handle_info({CloseTag, Socket}, StateName, - #state{socket = Socket, close_tag = CloseTag, + #state{static_env = #static_env{socket = Socket, close_tag = CloseTag}, socket_options = #socket_options{active = Active}, protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}, user_data_buffer = Buffer, + protocol_specific = PS, negotiated_version = Version} = State) -> %% Note that as of TLS 1.1, @@ -986,12 +1084,13 @@ handle_info({CloseTag, Socket}, StateName, end, ssl_connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - ssl_connection:stop({shutdown, transport_closed}, State); + {stop, {shutdown, transport_closed}, State}; true -> %% Fixes non-delivery of final TLS record in {active, once}. %% Basically allows the application the opportunity to set {active, once} again - %% and then receive the final message. - next_event(StateName, no_record, State) + %% and then receive the final message. Set internal active_n to zero + %% to ensure socket close message is sent if there is not enough data to deliver. + next_event(StateName, no_record, State#state{protocol_specific = PS#{active_n_toggle => true}}) end; handle_info({'EXIT', Sender, Reason}, _, #state{protocol_specific = #{sender := Sender}} = State) -> @@ -1003,6 +1102,12 @@ handle_alerts([], Result) -> Result; handle_alerts(_, {stop, _, _} = Stop) -> Stop; +handle_alerts([#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} | _Alerts], + {next_state, connection = StateName, #state{user_data_buffer = Buffer, + protocol_buffers = #protocol_buffers{tls_cipher_texts = CTs}} = + State}) when (Buffer =/= <<>>) orelse + (CTs =/= []) -> + {next_state, StateName, State#state{terminated = true}}; 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}) -> diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index 04bcea1e1b..a20499972b 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -134,67 +134,57 @@ start(internal, end. -%% TODO: move these functions + +negotiated(internal, + Map, + #state{connection_states = ConnectionStates0, + session = #session{session_id = SessionId, + own_certificate = OwnCert}, + ssl_options = #ssl_options{} = SslOpts, + key_share = KeyShare, + handshake_env = #handshake_env{tls_handshake_history = HHistory0}, + private_key = CertPrivateKey, + static_env = #static_env{ + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + socket = Socket, + transport_cb = Transport}} = State0, _Module) -> + Env = #{connection_states => ConnectionStates0, + session_id => SessionId, + own_certificate => OwnCert, + cert_db => CertDbHandle, + cert_db_ref => CertDbRef, + ssl_options => SslOpts, + key_share => KeyShare, + tls_handshake_history => HHistory0, + transport_cb => Transport, + socket => Socket, + private_key => CertPrivateKey}, + case tls_handshake_1_3:do_negotiated(Map, Env) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, negotiated, State0); + M -> + %% TODO: implement update_state + %% State = update_state(State0, M), + {next_state, wait_flight2, State0, [{next_event, internal, M}]} + + end. + + update_state(#state{connection_states = ConnectionStates0, session = Session} = State, - #{client_random := ClientRandom, - cipher := Cipher, + #{cipher := Cipher, key_share := KeyShare, session_id := SessionId}) -> #{security_parameters := SecParamsR0} = PendingRead = maps:get(pending_read, ConnectionStates0), #{security_parameters := SecParamsW0} = PendingWrite = maps:get(pending_write, ConnectionStates0), - SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, ClientRandom, Cipher), - SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, ClientRandom, Cipher), + SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher), + SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher), ConnectionStates = ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR}, pending_write => PendingWrite#{security_parameters => SecParamsW}}, State#state{connection_states = ConnectionStates, key_share = KeyShare, session = Session#session{session_id = SessionId}}. - - -negotiated(internal, - Map, - #state{connection_states = ConnectionStates0, - session = #session{session_id = SessionId}, - ssl_options = #ssl_options{} = SslOpts, - key_share = KeyShare, - tls_handshake_history = HHistory0, - transport_cb = Transport, - socket = Socket}, _Module) -> - - %% Create server_hello - %% Extensions: supported_versions, key_share, (pre_shared_key) - ServerHello = tls_handshake_1_3:server_hello(SessionId, KeyShare, - ConnectionStates0, Map), - - %% Update handshake_history (done in encode!) - %% Encode handshake - {BinMsg, _ConnectionStates, _HHistory} = - tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0), - %% Send server_hello - tls_connection:send(Transport, Socket, BinMsg), - Report = #{direction => outbound, - protocol => 'tls_record', - message => BinMsg}, - Msg = #{direction => outbound, - protocol => 'handshake', - message => ServerHello}, - ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}), - ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - ok. - - %% K_send = handshake ??? - %% (Send EncryptedExtensions) - %% ([Send CertificateRequest]) - %% [Send Certificate + CertificateVerify] - %% Send Finished - %% K_send = application ??? - - %% Will be called implicitly - %% {Record, State} = Connection:next_record(State2#state{session = Session}), - %% Connection:next_event(wait_flight2, Record, State, Actions), - %% OR - %% Connection:next_event(WAIT_EOED, Record, State, Actions) diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 5aca4bf8c8..f0bbd0f94f 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -232,7 +232,8 @@ hello(#client_hello{client_version = ClientVersion, %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- --spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist(). +-spec encode_handshake(tls_handshake() | tls_handshake_1_3:tls_handshake_1_3(), + tls_record:tls_version()) -> iolist(). %% %% Description: Encode a handshake packet %%-------------------------------------------------------------------- @@ -318,8 +319,6 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, Version, SslOpts, Session0, ConnectionStates0, Renegotiation) of - #alert{} = Alert -> - Alert; {Session, ConnectionStates, Protocol, ServerHelloExt} -> {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} @@ -330,14 +329,14 @@ handle_client_hello_extensions(Version, Type, Random, CipherSuites, handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> - case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, + try ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, Compression, HelloExt, Version, SslOpt, ConnectionStates0, - Renegotiation) of - #alert{} = Alert -> - Alert; + Renegotiation) of {ConnectionStates, ProtoExt, Protocol} -> {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + catch throw:Alert -> + Alert end. @@ -403,14 +402,15 @@ get_tls_handshake_aux(_Version, Data, _, Acc) -> decode_handshake({3, N}, ?HELLO_REQUEST, <<>>) when N < 4 -> #hello_request{}; -decode_handshake(Version, ?CLIENT_HELLO, +decode_handshake(Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, ?BYTE(SID_length), Session_ID:SID_length/binary, ?UINT16(Cs_length), CipherSuites:Cs_length/binary, ?BYTE(Cm_length), Comp_methods:Cm_length/binary, Extensions/binary>>) -> Exts = ssl_handshake:decode_vector(Extensions), - DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, client_hello), + DecodedExtensions = ssl_handshake:decode_hello_extensions(Exts, Version, {Major, Minor}, + client_hello), #client_hello{ client_version = {Major,Minor}, random = Random, diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index f381e038cf..670c4d424d 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -27,6 +27,7 @@ -include("tls_handshake_1_3.hrl"). -include("ssl_alert.hrl"). +-include("ssl_cipher.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). -include_lib("public_key/include/public_key.hrl"). @@ -38,7 +39,11 @@ -export([handle_client_hello/3]). %% Create handshake messages --export([server_hello/4]). +-export([certificate/5, + certificate_verify/5, + server_hello/4]). + +-export([do_negotiated/2]). %%==================================================================== %% Create handshake messages @@ -50,8 +55,7 @@ server_hello(SessionId, KeyShare, ConnectionStates, _Map) -> Extensions = server_hello_extensions(KeyShare), #server_hello{server_version = {3,3}, %% legacy_version cipher_suite = SecParams#security_parameters.cipher_suite, - compression_method = - SecParams#security_parameters.compression_algorithm, + compression_method = 0, %% legacy attribute random = SecParams#security_parameters.server_random, session_id = SessionId, extensions = Extensions @@ -63,6 +67,37 @@ server_hello_extensions(KeyShare) -> ssl_handshake:add_server_share(Extensions, KeyShare). +%% TODO: use maybe monad for error handling! +certificate(OwnCert, CertDbHandle, CertDbRef, _CRContext, server) -> + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of + {ok, _, Chain} -> + CertList = chain_to_cert_list(Chain), + %% If this message is in response to a CertificateRequest, the value of + %% certificate_request_context in that message. Otherwise (in the case + %%of server authentication), this field SHALL be zero length. + #certificate_1_3{ + certificate_request_context = <<>>, + certificate_list = CertList}; + {error, Error} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {server_has_no_suitable_certificates, Error}) + end. + +%% TODO: use maybe monad for error handling! +certificate_verify(OwnCert, PrivateKey, SignatureScheme, Messages, server) -> + {HashAlgo, _, _} = + ssl_cipher:scheme_to_components(SignatureScheme), + + %% Transcript-Hash(Handshake Context, Certificate) + Context = [Messages, OwnCert], + THash = tls_v1:transcript_hash(Context, HashAlgo), + + Signature = digitally_sign(THash, <<"TLS 1.3, server CertificateVerify">>, + HashAlgo, PrivateKey), + + #certificate_verify_1_3{ + algorithm = SignatureScheme, + signature = Signature + }. %%==================================================================== %% Encode handshake @@ -76,7 +111,7 @@ encode_handshake(#certificate_request_1_3{ {?CERTIFICATE_REQUEST, <<EncContext/binary, BinExts/binary>>}; encode_handshake(#certificate_1_3{ certificate_request_context = Context, - entries = Entries}) -> + certificate_list = Entries}) -> EncContext = encode_cert_req_context(Context), EncEntries = encode_cert_entries(Entries), {?CERTIFICATE, <<EncContext/binary, EncEntries/binary>>}; @@ -120,14 +155,14 @@ decode_handshake(?CERTIFICATE, <<?BYTE(0), ?UINT24(Size), Certs:Size/binary>>) - CertList = decode_cert_entries(Certs), #certificate_1_3{ certificate_request_context = <<>>, - entries = CertList + certificate_list = CertList }; decode_handshake(?CERTIFICATE, <<?BYTE(CSize), Context:CSize/binary, ?UINT24(Size), Certs:Size/binary>>) -> CertList = decode_cert_entries(Certs), #certificate_1_3{ certificate_request_context = Context, - entries = CertList + certificate_list = CertList }; decode_handshake(?ENCRYPTED_EXTENSIONS, <<?UINT16(Size), EncExts:Size/binary>>) -> #encrypted_extensions{ @@ -193,12 +228,60 @@ extensions_list(HelloExtensions) -> [Ext || {_, Ext} <- maps:to_list(HelloExtensions)]. +%% TODO: add extensions! +chain_to_cert_list(L) -> + chain_to_cert_list(L, []). +%% +chain_to_cert_list([], Acc) -> + lists:reverse(Acc); +chain_to_cert_list([H|T], Acc) -> + chain_to_cert_list(T, [certificate_entry(H)|Acc]). + + +certificate_entry(DER) -> + #certificate_entry{ + data = DER, + extensions = #{} %% Extensions not supported. + }. + +%% The digital signature is then computed over the concatenation of: +%% - A string that consists of octet 32 (0x20) repeated 64 times +%% - The context string +%% - A single 0 byte which serves as the separator +%% - The content to be signed +%% +%% For example, if the transcript hash was 32 bytes of 01 (this length +%% would make sense for SHA-256), the content covered by the digital +%% signature for a server CertificateVerify would be: +%% +%% 2020202020202020202020202020202020202020202020202020202020202020 +%% 2020202020202020202020202020202020202020202020202020202020202020 +%% 544c5320312e332c207365727665722043657274696669636174655665726966 +%% 79 +%% 00 +%% 0101010101010101010101010101010101010101010101010101010101010101 +digitally_sign(THash, Context, HashAlgo, PrivateKey = #'RSAPrivateKey'{}) -> + Content = build_content(Context, THash), + + %% The length of the Salt MUST be equal to the length of the output + %% of the digest algorithm. + PadLen = ssl_cipher:hash_size(HashAlgo), + + public_key:sign(Content, HashAlgo, PrivateKey, + [{rsa_padding, rsa_pkcs1_pss_padding}, + {rsa_pss_saltlen, PadLen}]). + + +build_content(Context, THash) -> + <<" ", + " ", + Context/binary,?BYTE(0),THash/binary>>. + %%==================================================================== %% Handle handshake messages %%==================================================================== handle_client_hello(#client_hello{cipher_suites = ClientCiphers, - random = Random, session_id = SessionId, extensions = Extensions} = _Hello, #ssl_options{ciphers = ServerCiphers, @@ -233,26 +316,24 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), Group = Maybe(select_server_group(ServerGroups, ClientGroups)), Maybe(validate_key_share(ClientGroups, ClientShares)), - _ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)), - %% Handle certificate - {PublicKeyAlgo, SignAlgo} = get_certificate_params(Cert), + ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)), + + {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), %% Check if client supports signature algorithm of server certificate - Maybe(check_cert_sign_algo(SignAlgo, ClientSignAlgs, ClientSignAlgsCert)), + Maybe(check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, ClientSignAlgsCert)), - %% Check if server supports + %% Select signature algorithm (used in CertificateVerify message). SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)), %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - _Ret = #{cipher => Cipher, group => Group, sign_alg => SelectedSignAlg, - %% client_share => ClientPubKey, + client_share => ClientPubKey, key_share => KeyShare, - client_random => Random, session_id => SessionId} %% TODO: @@ -265,9 +346,9 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); {Ref, illegal_parameter} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); - {Ref, {client_hello_retry_request, _Group0}} -> + {Ref, {hello_retry_request, _Group0}} -> %% TODO - exit({client_hello_retry_request, not_implemented}); + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, "hello_retry_request not implemented"); {Ref, no_suitable_cipher} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> @@ -277,6 +358,197 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, end. +do_negotiated(#{client_share := ClientKey, + group := SelectedGroup, + sign_alg := SignatureScheme + } = Map, + #{connection_states := ConnectionStates0, + session_id := SessionId, + own_certificate := OwnCert, + cert_db := CertDbHandle, + cert_db_ref := CertDbRef, + ssl_options := SslOpts, + key_share := KeyShare, + tls_handshake_history := HHistory0, + transport_cb := Transport, + socket := Socket, + private_key := CertPrivateKey}) -> + {Ref,Maybe} = maybe(), + + try + %% Create server_hello + %% Extensions: supported_versions, key_share, (pre_shared_key) + ServerHello = server_hello(SessionId, KeyShare, ConnectionStates0, Map), + + %% Update handshake_history (done in encode!) + %% Encode handshake + {BinMsg, ConnectionStates1, HHistory1} = + tls_connection:encode_handshake(ServerHello, {3,4}, ConnectionStates0, HHistory0), + %% Send server_hello + tls_connection:send(Transport, Socket, BinMsg), + log_handshake(SslOpts, ServerHello), + log_tls_record(SslOpts, BinMsg), + + %% ConnectionStates2 = calculate_security_parameters(ClientKey, SelectedGroup, KeyShare, + %% HHistory1, ConnectionStates1), + {HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV} = + calculate_security_parameters(ClientKey, SelectedGroup, KeyShare, + HHistory1, ConnectionStates1), + ConnectionStates2 = + update_pending_connection_states(ConnectionStates1, HandshakeSecret, + ReadKey, ReadIV, WriteKey, WriteIV), + ConnectionStates3 = + ssl_record:step_encryption_state(ConnectionStates2), + + %% Create Certificate + Certificate = certificate(OwnCert, CertDbHandle, CertDbRef, <<>>, server), + + %% Encode Certificate + {_, _ConnectionStates4, HHistory2} = + tls_connection:encode_handshake(Certificate, {3,4}, ConnectionStates3, HHistory1), + %% log_handshake(SslOpts, Certificate), + + %% Create CertificateVerify + {Messages, _} = HHistory2, + + %% Use selected signature_alg from here, HKDF only used for key_schedule + CertificateVerify = + tls_handshake_1_3:certificate_verify(OwnCert, CertPrivateKey, SignatureScheme, + Messages, server), + io:format("### CertificateVerify: ~p~n", [CertificateVerify]), + + %% Encode CertificateVerify + + %% Send Certificate, CertifricateVerify + + %% Send finished + + %% Next record/Next event + + Maybe(not_implemented(negotiated)) + + + catch + {Ref, {state_not_implemented, State}} -> + %% TODO + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State}) + end. + + +%% TODO: Remove this function! +not_implemented(State) -> + {error, {state_not_implemented, State}}. + + +log_handshake(SslOpts, Message) -> + Msg = #{direction => outbound, + protocol => 'handshake', + message => Message}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Msg, #{domain => [otp,ssl,handshake]}). + + +log_tls_record(SslOpts, BinMsg) -> + Report = #{direction => outbound, + protocol => 'tls_record', + message => BinMsg}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}). + + +calculate_security_parameters(ClientKey, SelectedGroup, KeyShare, HHistory, ConnectionStates) -> + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{prf_algorithm = HKDFAlgo, + cipher_suite = CipherSuite} = SecParamsR, + + %% Calculate handshake_secret + EarlySecret = tls_v1:key_schedule(early_secret, HKDFAlgo , {psk, <<>>}), + PrivateKey = get_server_private_key(KeyShare), %% #'ECPrivateKey'{} + + IKM = calculate_shared_secret(ClientKey, PrivateKey, SelectedGroup), + HandshakeSecret = tls_v1:key_schedule(handshake_secret, HKDFAlgo, IKM, EarlySecret), + + %% Calculate [sender]_handshake_traffic_secret + {Messages, _} = HHistory, + ClientHSTrafficSecret = + tls_v1:client_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), + ServerHSTrafficSecret = + tls_v1:server_handshake_traffic_secret(HKDFAlgo, HandshakeSecret, lists:reverse(Messages)), + + %% Calculate traffic keys + #{cipher := Cipher} = ssl_cipher_format:suite_definition(CipherSuite), + {ReadKey, ReadIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ClientHSTrafficSecret), + {WriteKey, WriteIV} = tls_v1:calculate_traffic_keys(HKDFAlgo, Cipher, ServerHSTrafficSecret), + + {HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV}. + + %% %% Update pending connection state + %% PendingRead0 = ssl_record:pending_connection_state(ConnectionStates, read), + %% PendingWrite0 = ssl_record:pending_connection_state(ConnectionStates, write), + + %% PendingRead = update_conn_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV), + %% PendingWrite = update_conn_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV), + + %% %% Update pending and copy to current (activate) + %% %% All subsequent handshake messages are encrypted + %% %% ([sender]_handshake_traffic_secret) + %% #{current_read => PendingRead, + %% current_write => PendingWrite, + %% pending_read => PendingRead, + %% pending_write => PendingWrite}. + + +get_server_private_key(#key_share_server_hello{server_share = ServerShare}) -> + get_private_key(ServerShare). + +get_private_key(#key_share_entry{ + key_exchange = #'ECPrivateKey'{} = PrivateKey}) -> + PrivateKey; +get_private_key(#key_share_entry{ + key_exchange = + {_, PrivateKey}}) -> + PrivateKey. + +%% X25519, X448 +calculate_shared_secret(OthersKey, MyKey, Group) + when is_binary(OthersKey) andalso is_binary(MyKey) andalso + (Group =:= x25519 orelse Group =:= x448)-> + crypto:compute_key(ecdh, OthersKey, MyKey, Group); +%% FFDHE +calculate_shared_secret(OthersKey, MyKey, Group) + when is_binary(OthersKey) andalso is_binary(MyKey) -> + Params = #'DHParameter'{prime = P} = ssl_dh_groups:dh_params(Group), + S = public_key:compute_key(OthersKey, MyKey, Params), + Size = byte_size(binary:encode_unsigned(P)), + ssl_cipher:add_zero_padding(S, Size); +%% ECDHE +calculate_shared_secret(OthersKey, MyKey = #'ECPrivateKey'{}, _Group) + when is_binary(OthersKey) -> + Point = #'ECPoint'{point = OthersKey}, + public_key:compute_key(Point, MyKey). + + +update_pending_connection_states(CS = #{pending_read := PendingRead0, + pending_write := PendingWrite0}, + HandshakeSecret, ReadKey, ReadIV, WriteKey, WriteIV) -> + PendingRead = update_connection_state(PendingRead0, HandshakeSecret, ReadKey, ReadIV), + PendingWrite = update_connection_state(PendingWrite0, HandshakeSecret, WriteKey, WriteIV), + CS#{pending_read => PendingRead, + pending_write => PendingWrite}. + +update_connection_state(ConnectionState = #{security_parameters := SecurityParameters0}, + HandshakeSecret, Key, IV) -> + %% Store secret + SecurityParameters = SecurityParameters0#security_parameters{ + master_secret = HandshakeSecret}, + ConnectionState#{security_parameters => SecurityParameters, + cipher_state => cipher_init(Key, IV)}. + + + +cipher_init(Key, IV) -> + #cipher_state{key = Key, iv = IV, tag_len = 16}. + + %% If there is no overlap between the received %% "supported_groups" and the groups supported by the server, then the %% server MUST abort the handshake with a "handshake_failure" or an @@ -324,14 +596,20 @@ get_client_public_key(Group, ClientShares) -> {value, {_, _, ClientPublicKey}} -> {ok, ClientPublicKey}; false -> - %% ClientHelloRetryRequest - {error, {client_hello_retry_request, Group}} + %% 4.1.4. Hello Retry Request + %% + %% The server will send this message in response to a ClientHello + %% message if it is able to find an acceptable set of parameters but the + %% ClientHello does not contain sufficient information to proceed with + %% the handshake. + {error, {hello_retry_request, Group}} end. select_cipher_suite([], _) -> {error, no_suitable_cipher}; select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> - case lists:member(Cipher, ServerCiphers) of + case lists:member(Cipher, tls_v1:suites('TLS_v1.3')) andalso + lists:member(Cipher, ServerCiphers) of true -> {ok, Cipher}; false -> @@ -349,22 +627,28 @@ select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> %% If no "signature_algorithms_cert" extension is %% present, then the "signature_algorithms" extension also applies to %% signatures appearing in certificates. -check_cert_sign_algo(SignAlgo, ClientSignAlgs, undefined) -> - maybe_lists_member(SignAlgo, ClientSignAlgs, - {insufficient_security, no_suitable_signature_algorithm}); -check_cert_sign_algo(SignAlgo, _, ClientSignAlgsCert) -> - maybe_lists_member(SignAlgo, ClientSignAlgsCert, - {insufficient_security, no_suitable_signature_algorithm}). + +%% Check if the signature algorithm of the server certificate is supported +%% by the client. +check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs, undefined) -> + do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgs); +check_cert_sign_algo(SignAlgo, SignHash, _, ClientSignAlgsCert) -> + do_check_cert_sign_algo(SignAlgo, SignHash, ClientSignAlgsCert). %% DSA keys are not supported by TLS 1.3 select_sign_algo(dsa, _ClientSignAlgs, _ServerSignAlgs) -> {error, {insufficient_security, no_suitable_public_key}}; -%% TODO: Implement check for ellipctic curves! +%% TODO: Implement support for ECDSA keys! +select_sign_algo(_, [], _) -> + {error, {insufficient_security, no_suitable_signature_algorithm}}; select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> {_, S, _} = ssl_cipher:scheme_to_components(C), - case PublicKeyAlgo =:= rsa andalso - ((S =:= rsa_pkcs1) orelse (S =:= rsa_pss_rsae) orelse (S =:= rsa_pss_pss)) andalso + %% RSASSA-PKCS1-v1_5 and Legacy algorithms are not defined for use in signed + %% TLS handshake messages: filter sha-1 and rsa_pkcs1. + case ((PublicKeyAlgo =:= rsa andalso S =:= rsa_pss_rsae) + orelse (PublicKeyAlgo =:= rsa_pss andalso S =:= rsa_pss_rsae)) + andalso lists:member(C, ServerSignAlgs) of true -> {ok, C}; @@ -373,51 +657,51 @@ select_sign_algo(PublicKeyAlgo, [C|ClientSignAlgs], ServerSignAlgs) -> end. -maybe_lists_member(Elem, List, Error) -> - case lists:member(Elem, List) of +do_check_cert_sign_algo(_, _, []) -> + {error, {insufficient_security, no_suitable_signature_algorithm}}; +do_check_cert_sign_algo(SignAlgo, SignHash, [Scheme|T]) -> + {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme), + case compare_sign_algos(SignAlgo, SignHash, Sign, Hash) of true -> ok; - false -> - {error, Error} + _Else -> + do_check_cert_sign_algo(SignAlgo, SignHash, T) end. -%% TODO: test with ecdsa, rsa_pss_rsae, rsa_pss_pss + +%% id-RSASSA-PSS (rsa_pss) indicates that the key may only be used for PSS signatures. +%% TODO: Uncomment when rsa_pss signatures are supported in certificates +%% compare_sign_algos(rsa_pss, Hash, Algo, Hash) +%% when Algo =:= rsa_pss_pss -> +%% true; +%% rsaEncryption (rsa) allows the key to be used for any of the standard encryption or +%% signature schemes. +compare_sign_algos(rsa, Hash, Algo, Hash) + when Algo =:= rsa_pss_rsae orelse + Algo =:= rsa_pkcs1 -> + true; +compare_sign_algos(Algo, Hash, Algo, Hash) -> + true; +compare_sign_algos(_, _, _, _) -> + false. + + get_certificate_params(Cert) -> {SignAlgo0, _Param, PublicKeyAlgo0} = ssl_handshake:get_cert_params(Cert), - SignAlgo = public_key:pkix_sign_types(SignAlgo0), + {SignHash0, SignAlgo} = public_key:pkix_sign_types(SignAlgo0), + %% Convert hash to new format + SignHash = case SignHash0 of + sha -> + sha1; + H -> H + end, PublicKeyAlgo = public_key_algo(PublicKeyAlgo0), - Scheme = sign_algo_to_scheme(SignAlgo), - {PublicKeyAlgo, Scheme}. - -sign_algo_to_scheme({Hash0, Sign0}) -> - SupportedSchemes = tls_v1:default_signature_schemes({3,4}), - Hash = case Hash0 of - sha -> - sha1; - H -> - H - end, - Sign = case Sign0 of - rsa -> - rsa_pkcs1; - S -> - S - end, - sign_algo_to_scheme(Hash, Sign, SupportedSchemes). -%% -sign_algo_to_scheme(_, _, []) -> - not_found; -sign_algo_to_scheme(H, S, [Scheme|T]) -> - {Hash, Sign, _Curve} = ssl_cipher:scheme_to_components(Scheme), - case H =:= Hash andalso S =:= Sign of - true -> - Scheme; - false -> - sign_algo_to_scheme(H, S, T) - end. + {PublicKeyAlgo, SignAlgo, SignHash}. %% Note: copied from ssl_handshake +public_key_algo(?'id-RSASSA-PSS') -> + rsa_pss; public_key_algo(?rsaEncryption) -> rsa; public_key_algo(?'id-ecPublicKey') -> diff --git a/lib/ssl/src/tls_handshake_1_3.hrl b/lib/ssl/src/tls_handshake_1_3.hrl index 6ef5364399..7ae1b93e1c 100644 --- a/lib/ssl/src/tls_handshake_1_3.hrl +++ b/lib/ssl/src/tls_handshake_1_3.hrl @@ -191,7 +191,7 @@ %% case RawPublicKey: %% /* From RFC 7250 ASN.1_subjectPublicKeyInfo */ %% opaque ASN1_subjectPublicKeyInfo<1..2^24-1>; - + %% %% case X509: %% opaque cert_data<1..2^24-1>; %% }; @@ -200,9 +200,14 @@ -record(certificate_1_3, { certificate_request_context, % opaque certificate_request_context<0..2^8-1>; - entries % CertificateEntry certificate_list<0..2^24-1>; + certificate_list % CertificateEntry certificate_list<0..2^24-1>; }). +-record(certificate_verify_1_3, { + algorithm, % SignatureScheme + signature % signature<0..2^16-1> + }). + %% RFC 8446 B.3.4. Ticket Establishment -record(new_session_ticket, { ticket_lifetime, %unit32 @@ -223,4 +228,11 @@ request_update }). +-type tls_handshake_1_3() :: #encrypted_extensions{} | + #certificate_request_1_3{} | + #certificate_1_3{} | + #certificate_verify_1_3{}. + +-export_type([tls_handshake_1_3/0]). + -endif. % -ifdef(tls_handshake_1_3). diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 50fad2e680..b8bf4603dd 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -76,25 +76,15 @@ init_connection_states(Role, BeastMitigation) -> pending_write => Pending}. %%-------------------------------------------------------------------- --spec get_tls_records(binary(), [tls_version()], binary(), ssl_options()) -> {[binary()], binary()} | #alert{}. +-spec get_tls_records(binary(), [tls_version()] | tls_version(), binary(), + #ssl_options{}) -> {[binary()], binary()} | #alert{}. %% %% 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, Versions, Buffer, SslOpts) -> - BinData = list_to_binary([Buffer, Data]), - case erlang:byte_size(BinData) of - N when N >= 3 -> - case assert_version(BinData, Versions) of - true -> - get_tls_records_aux(BinData, [], SslOpts); - false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end; - _ -> - get_tls_records_aux(BinData, [], SslOpts) - end. +get_tls_records(Data, Version, Buffer, SslOpts) -> + get_tls_records_aux(Version, <<Buffer/binary, Data/binary>>, [], SslOpts). %%==================================================================== %% Encoding @@ -116,7 +106,7 @@ encode_handshake(Frag, Version, ConnectionStates) -> 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), + Data = split_bin(iolist_to_binary(Frag), Version, BCA, BeastMitigation), encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates); _ -> encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) @@ -157,7 +147,7 @@ encode_data(Frag, Version, security_parameters := #security_parameters{bulk_cipher_algorithm = BCA}}} = ConnectionStates) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), + Data = split_bin(Frag, Version, BCA, BeastMitigation), encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). %%==================================================================== @@ -408,72 +398,59 @@ initial_connection_state(ConnectionEnd, BeastMitigation) -> server_verify_data => undefined }. -assert_version(<<?BYTE(_), ?BYTE(MajVer), ?BYTE(MinVer), _/binary>>, Versions) -> - is_acceptable_version({MajVer, MinVer}, Versions). - -get_tls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc, SslOpts) -> - RawTLSRecord = <<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary>>, - Report = #{direction => inbound, - protocol => 'tls_record', - message => [RawTLSRecord]}, - ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - get_tls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, - version = {MajVer, MinVer}, - fragment = Data} | Acc], - SslOpts); -get_tls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), - Data:Length/binary, Rest/binary>>, Acc, SslOpts) -> - RawTLSRecord = <<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary>>, - Report = #{direction => inbound, - protocol => 'tls_record', - message => [RawTLSRecord]}, - ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - get_tls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, - version = {MajVer, MinVer}, - fragment = Data} | Acc], - SslOpts); -get_tls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, - Rest/binary>>, Acc, SslOpts) -> - RawTLSRecord = <<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary>>, - Report = #{direction => inbound, - protocol => 'tls_record', - message => [RawTLSRecord]}, - ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - get_tls_records_aux(Rest, [#ssl_tls{type = ?ALERT, - version = {MajVer, MinVer}, - fragment = Data} | Acc], - SslOpts); -get_tls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary, Rest/binary>>, - Acc, SslOpts) -> - RawTLSRecord = <<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), - ?UINT16(Length), Data:Length/binary>>, +get_tls_records_aux({MajVer, MinVer} = Version, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawTLSRecord, + Acc, SslOpts) when Type == ?APPLICATION_DATA; + Type == ?HANDSHAKE; + Type == ?ALERT; + Type == ?CHANGE_CIPHER_SPEC -> Report = #{direction => inbound, protocol => 'tls_record', message => [RawTLSRecord]}, ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), - get_tls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, - version = {MajVer, MinVer}, - fragment = Data} | Acc], - SslOpts); -get_tls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), - ?UINT16(Length), _/binary>>, - _Acc, _SslOpts) when Length > ?MAX_CIPHER_TEXT_LENGTH -> + get_tls_records_aux(Version, Rest, [#ssl_tls{type = Type, + version = Version, + fragment = Data} | Acc], SslOpts); +get_tls_records_aux(Versions, <<?BYTE(Type),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Length), Data:Length/binary, Rest/binary>> = RawTLSRecord, + Acc, SslOpts) when is_list(Versions) andalso + ((Type == ?APPLICATION_DATA) + orelse + (Type == ?HANDSHAKE) + orelse + (Type == ?ALERT) + orelse + (Type == ?CHANGE_CIPHER_SPEC)) -> + case is_acceptable_version({MajVer, MinVer}, Versions) of + true -> + Report = #{direction => inbound, + protocol => 'tls_record', + message => [RawTLSRecord]}, + ssl_logger:debug(SslOpts#ssl_options.log_level, Report, #{domain => [otp,ssl,tls_record]}), + get_tls_records_aux(Versions, Rest, [#ssl_tls{type = Type, + version = {MajVer, MinVer}, + fragment = Data} | Acc], SslOpts); + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; +get_tls_records_aux(_, <<?BYTE(Type),?BYTE(_MajVer),?BYTE(_MinVer), + ?UINT16(Length), _:Length/binary, _Rest/binary>>, + _, _) when Type == ?APPLICATION_DATA; + Type == ?HANDSHAKE; + Type == ?ALERT; + Type == ?CHANGE_CIPHER_SPEC -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC); +get_tls_records_aux(_, <<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), + ?UINT16(Length), _/binary>>, + _Acc, _) when Length > ?MAX_CIPHER_TEXT_LENGTH -> ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); -get_tls_records_aux(Data, Acc, _SslOpts) -> +get_tls_records_aux(_, Data, Acc, _) -> case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of true -> {lists:reverse(Acc), Data}; false -> ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) - end. + end. %%-------------------------------------------------------------------- encode_plain_text(Type, Version, Data, #{current_write := Write0} = ConnectionStates) -> {CipherFragment, Write1} = do_encode_plain_text(Type, Version, Data, Write0), @@ -526,27 +503,26 @@ start_additional_data(Type, {MajVer, MinVer}, %% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are %% not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when +split_bin(<<FirstByte:8, Rest/binary>>, Version, BCA, one_n_minus_one) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse {3, 0} == Version) -> - do_split_bin(Rest, ChunkSize, [[FirstByte]]); + [[FirstByte]|do_split_bin(Rest)]; %% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 %% splitting. -split_bin(Bin, ChunkSize, Version, BCA, zero_n) when +split_bin(Bin, Version, BCA, zero_n) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse {3, 0} == Version) -> - do_split_bin(Bin, ChunkSize, [[<<>>]]); -split_bin(Bin, ChunkSize, _, _, _) -> - do_split_bin(Bin, ChunkSize, []). + [<<>>|do_split_bin(Bin)]; +split_bin(Bin, _, _, _) -> + do_split_bin(Bin). -do_split_bin(<<>>, _, Acc) -> - lists:reverse(Acc); -do_split_bin(Bin, ChunkSize, Acc) -> +do_split_bin(<<>>) -> []; +do_split_bin(Bin) -> case Bin of - <<Chunk:ChunkSize/binary, Rest/binary>> -> - do_split_bin(Rest, ChunkSize, [Chunk | Acc]); + <<Chunk:?MAX_PLAIN_TEXT_LENGTH/binary, Rest/binary>> -> + [Chunk|do_split_bin(Rest)]; _ -> - lists:reverse(Acc, [Bin]) + [Bin] end. %%-------------------------------------------------------------------- lowest_list_protocol_version(Ver, []) -> diff --git a/lib/ssl/src/tls_record_1_3.erl b/lib/ssl/src/tls_record_1_3.erl index d424336187..1681babed9 100644 --- a/lib/ssl/src/tls_record_1_3.erl +++ b/lib/ssl/src/tls_record_1_3.erl @@ -76,8 +76,8 @@ encode_data(Frag, ConnectionStates) -> encode_plain_text(Type, Data0, #{current_write := Write0} = ConnectionStates) -> PadLen = 0, %% TODO where to specify PadLen? Data = inner_plaintext(Type, Data0, PadLen), - {CipherFragment, Write1} = encode_plain_text(Data, Write0), - {CipherText, Write} = encode_tls_cipher_text(CipherFragment, Write1), + CipherFragment = encode_plain_text(Data, Write0), + {CipherText, Write} = encode_tls_cipher_text(CipherFragment, Write0), {CipherText, ConnectionStates#{current_write => Write}}. encode_iolist(Type, Data, ConnectionStates0) -> @@ -105,24 +105,23 @@ decode_cipher_text(#ssl_tls{type = ?OPAQUE_TYPE, fragment = CipherFragment}, #{current_read := #{sequence_number := Seq, - cipher_state := CipherS0, + cipher_state := #cipher_state{key = Key, + iv = IV, + tag_len = TagLen}, security_parameters := #security_parameters{ cipher_type = ?AEAD, bulk_cipher_algorithm = BulkCipherAlgo} } = ReadState0} = ConnectionStates0) -> - AAD = start_additional_data(), - CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0), - case decipher_aead(BulkCipherAlgo, CipherS1, AAD, CipherFragment) of - {PlainFragment, CipherS1} -> + case decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) of + #alert{} = Alert -> + Alert; + PlainFragment -> ConnectionStates = ConnectionStates0#{current_read => - ReadState0#{cipher_state => CipherS1, - sequence_number => Seq + 1}}, - decode_inner_plaintext(PlainFragment, ConnectionStates); - #alert{} = Alert -> - Alert + ReadState0#{sequence_number => Seq + 1}}, + {decode_inner_plaintext(PlainFragment), ConnectionStates} end; decode_cipher_text(#ssl_tls{type = Type, version = ?LEGACY_VERSION, @@ -137,7 +136,7 @@ decode_cipher_text(#ssl_tls{type = Type, fragment = CipherFragment}, ConnnectionStates0}; decode_cipher_text(#ssl_tls{type = Type}, _) -> %% Version mismatch is already asserted - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_typ_mismatch, Type}). + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, {record_type_mismatch, Type}). %%-------------------------------------------------------------------- %%% Internal functions @@ -170,62 +169,61 @@ encode_plain_text(#inner_plaintext{ content = Data, type = Type, zeros = Zeros - }, #{cipher_state := CipherS0, + }, #{cipher_state := #cipher_state{key= Key, + iv = IV, + tag_len = TagLen}, sequence_number := Seq, security_parameters := #security_parameters{ - cipher_type = ?AEAD} - } = WriteState0) -> - PlainText = <<Data/binary, ?BYTE(Type), Zeros/binary>>, - AAD = start_additional_data(), - CipherS1 = ssl_cipher:nonce_seed(<<?UINT64(Seq)>>, CipherS0), - {Encoded, WriteState} = cipher_aead(PlainText, WriteState0#{cipher_state => CipherS1}, AAD), - {#tls_cipher_text{opaque_type = Type, - legacy_version = {3,3}, - encoded_record = Encoded}, WriteState}; + cipher_type = ?AEAD, + bulk_cipher_algorithm = BulkCipherAlgo} + }) -> + PlainText = [Data, Type, Zeros], + Encoded = cipher_aead(PlainText, BulkCipherAlgo, Key, Seq, IV, TagLen), + #tls_cipher_text{opaque_type = 23, %% 23 (application_data) for outward compatibility + legacy_version = {3,3}, + encoded_record = Encoded}; encode_plain_text(#inner_plaintext{ content = Data, type = Type }, #{security_parameters := #security_parameters{ cipher_suite = ?TLS_NULL_WITH_NULL_NULL} - } = WriteState0) -> + }) -> %% RFC8446 - 5.1. Record Layer %% When record protection has not yet been engaged, TLSPlaintext %% structures are written directly onto the wire. - {#tls_cipher_text{opaque_type = Type, + #tls_cipher_text{opaque_type = Type, legacy_version = {3,3}, - encoded_record = Data}, WriteState0}; + encoded_record = Data}; encode_plain_text(_, CS) -> exit({cs, CS}). -start_additional_data() -> - {MajVer, MinVer} = ?LEGACY_VERSION, - <<?BYTE(?OPAQUE_TYPE), ?BYTE(MajVer), ?BYTE(MinVer)>>. - -end_additional_data(AAD, Len) -> - <<AAD/binary, ?UINT16(Len)>>. - -nonce(#cipher_state{nonce = Nonce, iv = IV}) -> - Len = size(IV), - crypto:exor(<<Nonce:Len/bytes>>, IV). +additional_data(Length) -> + <<?BYTE(?OPAQUE_TYPE), ?BYTE(3), ?BYTE(3),?UINT16(Length)>>. -cipher_aead(Fragment, - #{cipher_state := CipherS0, - security_parameters := - #security_parameters{bulk_cipher_algorithm = - BulkCipherAlgo} - } = WriteState0, AAD) -> - {CipherFragment, CipherS1} = - cipher_aead(BulkCipherAlgo, CipherS0, AAD, Fragment), - {CipherFragment, WriteState0#{cipher_state => CipherS1}}. +%% The per-record nonce for the AEAD construction is formed as +%% follows: +%% +%% 1. The 64-bit record sequence number is encoded in network byte +%% order and padded to the left with zeros to iv_length. +%% +%% 2. The padded sequence number is XORed with either the static +%% client_write_iv or server_write_iv (depending on the role). +%% +%% The resulting quantity (of length iv_length) is used as the +%% per-record nonce. +nonce(Seq, IV) -> + Padding = binary:copy(<<0>>, byte_size(IV) - 8), + crypto:exor(<<Padding/binary,?UINT64(Seq)>>, IV). -cipher_aead(Type, #cipher_state{key=Key} = CipherState, AAD0, Fragment) -> - AAD = end_additional_data(AAD0, erlang:iolist_size(Fragment)), - Nonce = nonce(CipherState), - {Content, CipherTag} = ssl_cipher:aead_encrypt(Type, Key, Nonce, Fragment, AAD), - {<<Content/binary, CipherTag/binary>>, CipherState}. +cipher_aead(Fragment, BulkCipherAlgo, Key, Seq, IV, TagLen) -> + AAD = additional_data(erlang:iolist_size(Fragment) + TagLen), + Nonce = nonce(Seq, IV), + {Content, CipherTag} = + ssl_cipher:aead_encrypt(BulkCipherAlgo, Key, Nonce, Fragment, AAD), + <<Content/binary, CipherTag/binary>>. encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type, legacy_version = {MajVer, MinVer}, @@ -234,13 +232,14 @@ encode_tls_cipher_text(#tls_cipher_text{opaque_type = Type, {[<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Encoded], Write#{sequence_number => Seq +1}}. -decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment) -> +decipher_aead(CipherFragment, BulkCipherAlgo, Key, Seq, IV, TagLen) -> try - Nonce = nonce(CipherState), - {AAD, CipherText, CipherTag} = aead_ciphertext_split(CipherState, CipherFragment, AAD0), - case ssl_cipher:aead_decrypt(Type, Key, Nonce, CipherText, CipherTag, AAD) of + AAD = additional_data(erlang:iolist_size(CipherFragment)), + Nonce = nonce(Seq, IV), + {CipherText, CipherTag} = aead_ciphertext_split(CipherFragment, TagLen), + case ssl_cipher:aead_decrypt(BulkCipherAlgo, Key, Nonce, CipherText, CipherTag, AAD) of Content when is_binary(Content) -> - {Content, CipherState}; + Content; _ -> ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end @@ -249,39 +248,34 @@ decipher_aead(Type, #cipher_state{key = Key} = CipherState, AAD0, CipherFragment ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. -aead_ciphertext_split(#cipher_state{tag_len = Len}, CipherTextFragment, AAD) -> - CipherLen = size(CipherTextFragment) - Len, - <<CipherText:CipherLen/bytes, CipherTag:Len/bytes>> = CipherTextFragment, - {end_additional_data(AAD, CipherLen), CipherText, CipherTag}. -decode_inner_plaintext(PlainText, ConnnectionStates) -> - case remove_padding(PlainText) of - #alert{} = Alert -> - Alert; - {Data, Type} -> - {#ssl_tls{type = Type, - version = {3,4}, %% Internally use real version - fragment = Data}, ConnnectionStates} - end. +aead_ciphertext_split(CipherTextFragment, TagLen) + when is_binary(CipherTextFragment) -> + CipherLen = erlang:byte_size(CipherTextFragment) - TagLen, + <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> = CipherTextFragment, + {CipherText, CipherTag}; +aead_ciphertext_split(CipherTextFragment, TagLen) + when is_list(CipherTextFragment) -> + CipherLen = erlang:iolist_size(CipherTextFragment) - TagLen, + <<CipherText:CipherLen/bytes, CipherTag:TagLen/bytes>> = + erlang:iolist_to_binary(CipherTextFragment), + {CipherText, CipherTag}. -remove_padding(PlainText)-> - case binary:split(PlainText, <<0>>, [global, trim]) of - [] -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, padding_error); - [Content] -> - Type = binary:last(Content), - split_content(Type, Content, erlang:byte_size(Content) - 1) +decode_inner_plaintext(PlainText) -> + case binary:last(PlainText) of + 0 -> + decode_inner_plaintext(init_binary(PlainText)); + Type when Type =:= ?APPLICATION_DATA orelse + Type =:= ?HANDSHAKE orelse + Type =:= ?ALERT -> + #ssl_tls{type = Type, + version = {3,4}, %% Internally use real version + fragment = init_binary(PlainText)}; + _Else -> + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert) end. -split_content(?HANDSHAKE, _, 0) -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_handshake); -split_content(?ALERT, _, 0) -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_alert); -%% For special middlebox compatible case! -split_content(?CHANGE_CIPHER_SPEC, _, 0) -> - ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE, empty_change_cipher_spec); -split_content(?APPLICATION_DATA = Type, _, 0) -> - {Type, <<>>}; -split_content(Type, Content, N) -> - <<Data:N/bytes, ?BYTE(Type)>> = Content, - {Type, Data}. +init_binary(B) -> + {Init, _} = + split_binary(B, byte_size(B) - 1), + Init. diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index 75409143a8..1559fcbb37 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -29,7 +29,7 @@ %% API -export([start/0, start/1, initialize/2, send_data/2, send_alert/2, - send_and_ack_alert/2, setopts/2, renegotiate/1, + send_and_ack_alert/2, setopts/2, renegotiate/1, peer_renegotiate/1, downgrade/2, update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]). %% gen_statem callbacks @@ -103,7 +103,7 @@ send_alert(Pid, Alert) -> %% in the connection state and recive an ack. %%-------------------------------------------------------------------- send_and_ack_alert(Pid, Alert) -> - gen_statem:cast(Pid, {ack_alert, Alert}). + gen_statem:call(Pid, {ack_alert, Alert}, ?DEFAULT_TIMEOUT). %%-------------------------------------------------------------------- -spec setopts(pid(), [{packet, integer() | atom()}]) -> ok | {error, term()}. %% Description: Send application data @@ -119,6 +119,15 @@ setopts(Pid, Opts) -> renegotiate(Pid) -> %% Needs error handling for external API call(Pid, renegotiate). + +%%-------------------------------------------------------------------- +-spec peer_renegotiate(pid()) -> {ok, WriteState::map()} | {error, term()}. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when handshaking. +%%-------------------------------------------------------------------- +peer_renegotiate(Pid) -> + gen_statem:call(Pid, renegotiate, ?DEFAULT_TIMEOUT). + %%-------------------------------------------------------------------- -spec update_connection_state(pid(), WriteState::map(), tls_record:tls_version()) -> ok. %% Description: So TLS connection process can synchronize the @@ -126,6 +135,21 @@ renegotiate(Pid) -> %%-------------------------------------------------------------------- update_connection_state(Pid, NewState, Version) -> gen_statem:cast(Pid, {new_write, NewState, Version}). + +%%-------------------------------------------------------------------- +-spec downgrade(pid(), integer()) -> {ok, ssl_record:connection_state()} + | {error, timeout}. +%% Description: So TLS connection process can synchronize the +%% encryption state to be used when sending application data. +%%-------------------------------------------------------------------- +downgrade(Pid, Timeout) -> + try gen_statem:call(Pid, downgrade, Timeout) of + Result -> + Result + catch + _:_ -> + {error, timeout} + end. %%-------------------------------------------------------------------- -spec dist_handshake_complete(pid(), node(), term()) -> ok. %% Description: Erlang distribution callback @@ -203,8 +227,9 @@ connection({call, From}, renegotiate, #data{connection_states = #{current_write := Write}} = StateData) -> {next_state, handshake, StateData, [{reply, From, {ok, Write}}]}; connection({call, From}, {application_data, AppData}, - #data{socket_options = SockOpts} = StateData) -> - case encode_packet(AppData, SockOpts) of + #data{socket_options = #socket_options{packet = Packet}} = + StateData) -> + case encode_packet(Packet, AppData) of {error, _} = Error -> {next_state, ?FUNCTION_NAME, StateData, [{reply, From, Error}]}; Data -> @@ -220,17 +245,33 @@ connection({call, From}, dist_get_tls_socket, tracker = Tracker} = StateData) -> TLSSocket = Connection:socket([Pid, self()], Transport, Socket, Connection, Tracker), {next_state, ?FUNCTION_NAME, StateData, [{reply, From, {ok, TLSSocket}}]}; -connection({call, From}, {dist_handshake_complete, _Node, DHandle}, #data{connection_pid = Pid} = StateData) -> +connection({call, From}, {dist_handshake_complete, _Node, DHandle}, + #data{connection_pid = Pid, + socket_options = #socket_options{packet = Packet}} = + StateData) -> ok = erlang:dist_ctrl_input_handler(DHandle, Pid), ok = ssl_connection:dist_handshake_complete(Pid, DHandle), %% From now on we execute on normal priority process_flag(priority, normal), - Events = dist_data_events(DHandle, []), - {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, [{reply, From, ok} | Events]}; -connection(cast, {ack_alert, #alert{} = Alert}, #data{connection_pid = Pid} =StateData0) -> + {next_state, ?FUNCTION_NAME, StateData#data{dist_handle = DHandle}, + [{reply, From, ok} + | case dist_data(DHandle, Packet) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},Data}}] + end]}; +connection({call, From}, {ack_alert, #alert{} = Alert}, StateData0) -> StateData = send_tls_alert(Alert, StateData0), - Pid ! {self(), ack_alert}, - {next_state, ?FUNCTION_NAME, StateData}; + {next_state, ?FUNCTION_NAME, StateData, + [{reply,From,ok}]}; +connection({call, From}, downgrade, #data{connection_states = + #{current_write := Write}} = StateData) -> + {next_state, death_row, StateData, [{reply,From, {ok, Write}}]}; +connection(internal, {application_packets, From, Data}, StateData) -> + send_application_data(Data, From, ?FUNCTION_NAME, StateData); +%% connection(cast, #alert{} = Alert, StateData0) -> StateData = send_tls_alert(Alert, StateData0), {next_state, ?FUNCTION_NAME, StateData}; @@ -240,9 +281,19 @@ connection(cast, {new_write, WritesState, Version}, StateData#data{connection_states = ConnectionStates0#{current_write => WritesState}, negotiated_version = Version}}; -connection(info, dist_data, #data{dist_handle = DHandle} = StateData) -> - Events = dist_data_events(DHandle, []), - {next_state, ?FUNCTION_NAME, StateData, Events}; +%% +connection(info, dist_data, + #data{dist_handle = DHandle, + socket_options = #socket_options{packet = Packet}} = + StateData) -> + {next_state, ?FUNCTION_NAME, StateData, + case dist_data(DHandle, Packet) of + [] -> + []; + Data -> + [{next_event, internal, + {application_packets,{self(),undefined},Data}}] + end}; connection(info, tick, StateData) -> consume_ticks(), {next_state, ?FUNCTION_NAME, StateData, @@ -275,6 +326,8 @@ handshake(cast, {new_write, WritesState, Version}, StateData#data{connection_states = ConnectionStates0#{current_write => WritesState}, negotiated_version = Version}}; +handshake(internal, {application_packets,_,_}, _) -> + {keep_state_and_data, [postpone]}; handshake(info, Msg, StateData) -> handle_info(Msg, ?FUNCTION_NAME, StateData). @@ -351,12 +404,13 @@ send_application_data(Data, From, StateName, log_level = LogLevel} = StateData0) -> case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of true -> - ssl_connection:internal_renegotiation(Pid, ConnectionStates0), + ssl_connection:internal_renegotiation(Pid, ConnectionStates0), {next_state, handshake, StateData0, - [{next_event, {call, From}, {application_data, Data}}]}; + [{next_event, internal, {application_packets, From, Data}}]}; false -> {Msgs, ConnectionStates} = - Connection:encode_data(Data, Version, ConnectionStates0), + Connection:encode_data( + iolist_to_binary(Data), Version, ConnectionStates0), StateData = StateData0#data{connection_states = ConnectionStates}, case Connection:send(Transport, Socket, Msgs) of ok when DistHandle =/= undefined -> @@ -378,21 +432,18 @@ send_application_data(Data, From, StateName, end end. -encode_packet(Data, #socket_options{packet=Packet}) -> +-compile({inline, encode_packet/2}). +encode_packet(Packet, Data) -> + Len = iolist_size(Data), 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 -> - {error, {badarg, {packet_to_large, Len, Max}}}; - false -> - <<Len:Size, Bin/binary>> + 1 when Len < (1 bsl 8) -> [<<Len:8>>,Data]; + 2 when Len < (1 bsl 16) -> [<<Len:16>>,Data]; + 4 when Len < (1 bsl 32) -> [<<Len:32>>,Data]; + N when N =:= 1; N =:= 2; N =:= 4 -> + {error, + {badarg, {packet_to_large, Len, (1 bsl (Packet bsl 3)) - 1}}}; + _ -> + Data end. set_opts(SocketOptions, [{packet, N}]) -> @@ -426,14 +477,18 @@ call(FsmPid, Event) -> %%---------------Erlang distribution -------------------------------------- -dist_data_events(DHandle, Events) -> +dist_data(DHandle, Packet) -> case erlang:dist_ctrl_get_data(DHandle) of none -> erlang:dist_ctrl_get_data_notification(DHandle), - lists:reverse(Events); + []; Data -> - Event = {next_event, {call, {self(), undefined}}, {application_data, Data}}, - dist_data_events(DHandle, [Event | Events]) + %% This is encode_packet(4, Data) without Len check + %% since the emulator will always deliver a Data + %% smaller than 4 GB, and the distribution will + %% therefore always have to use {packet,4} + Len = iolist_size(Data), + [<<Len:32>>,Data|dist_data(DHandle, Packet)] end. consume_ticks() -> diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl index 83dd7585dd..df2a421bce 100644 --- a/lib/ssl/src/tls_v1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -36,7 +36,15 @@ default_signature_schemes/1, signature_schemes/2, groups/1, groups/2, group_to_enum/1, enum_to_group/1, default_groups/1]). --export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4]). +-export([derive_secret/4, hkdf_expand_label/5, hkdf_extract/3, hkdf_expand/4, + key_schedule/3, key_schedule/4, + external_binder_key/2, resumption_binder_key/2, + client_early_traffic_secret/3, early_exporter_master_secret/3, + client_handshake_traffic_secret/3, server_handshake_traffic_secret/3, + client_application_traffic_secret_0/3, server_application_traffic_secret_0/3, + exporter_master_secret/3, resumption_master_secret/3, + update_traffic_secret/2, calculate_traffic_keys/3, + transcript_hash/2]). -type named_curve() :: sect571r1 | sect571k1 | secp521r1 | brainpoolP512r1 | sect409k1 | sect409r1 | brainpoolP384r1 | secp384r1 | @@ -56,7 +64,7 @@ %% TLS 1.3 --------------------------------------------------- -spec derive_secret(Secret::binary(), Label::binary(), - Messages::binary(), Algo::ssl_cipher_format:hash()) -> Key::binary(). + Messages::iodata(), Algo::ssl_cipher_format:hash()) -> Key::binary(). derive_secret(Secret, Label, Messages, Algo) -> Hash = crypto:hash(mac_algo(Algo), Messages), hkdf_expand_label(Secret, Label, @@ -71,11 +79,14 @@ hkdf_expand_label(Secret, Label0, Context, Length, Algo) -> %% opaque label<7..255> = "tls13 " + Label; %% opaque context<0..255> = Context; %% } HkdfLabel; - Content = << <<"tls13">>/binary, Label0/binary, Context/binary>>, + Label1 = << <<"tls13 ">>/binary, Label0/binary>>, + LLen = size(Label1), + Label = <<?BYTE(LLen), Label1/binary>>, + Content = <<Label/binary, Context/binary>>, Len = size(Content), HkdfLabel = <<?UINT16(Len), Content/binary>>, hkdf_expand(Secret, HkdfLabel, Length, Algo). - + -spec hkdf_extract(MacAlg::ssl_cipher_format:hash(), Salt::binary(), KeyingMaterial::binary()) -> PseudoRandKey::binary(). @@ -89,6 +100,12 @@ hkdf_extract(MacAlg, Salt, KeyingMaterial) -> hkdf_expand(PseudoRandKey, ContextInfo, Length, Algo) -> Iterations = erlang:ceil(Length / ssl_cipher:hash_size(Algo)), hkdf_expand(Algo, PseudoRandKey, ContextInfo, Length, 1, Iterations, <<>>, <<>>). + + +-spec transcript_hash(Messages::iodata(), Algo::ssl_cipher_format:hash()) -> Hash::binary(). + +transcript_hash(Messages, Algo) -> + crypto:hash(mac_algo(Algo), Messages). %% TLS 1.3 --------------------------------------------------- %% TLS 1.0 -1.2 --------------------------------------------------- @@ -235,6 +252,153 @@ setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, ServerWriteKey, ClientIV, ServerIV}. %% TLS v1.2 --------------------------------------------------- +%% TLS v1.3 --------------------------------------------------- +%% RFC 8446 - 7.1. Key Schedule +%% +%% 0 +%% | +%% v +%% PSK -> HKDF-Extract = Early Secret +%% | +%% +-----> Derive-Secret(., "ext binder" | "res binder", "") +%% | = binder_key +%% | +%% +-----> Derive-Secret(., "c e traffic", ClientHello) +%% | = client_early_traffic_secret +%% | +%% +-----> Derive-Secret(., "e exp master", ClientHello) +%% | = early_exporter_master_secret +%% v +%% Derive-Secret(., "derived", "") +%% | +%% v +%% (EC)DHE -> HKDF-Extract = Handshake Secret +%% | +%% +-----> Derive-Secret(., "c hs traffic", +%% | ClientHello...ServerHello) +%% | = client_handshake_traffic_secret +%% | +%% +-----> Derive-Secret(., "s hs traffic", +%% | ClientHello...ServerHello) +%% | = server_handshake_traffic_secret +%% v +%% Derive-Secret(., "derived", "") +%% | +%% v +%% 0 -> HKDF-Extract = Master Secret +%% | +%% +-----> Derive-Secret(., "c ap traffic", +%% | ClientHello...server Finished) +%% | = client_application_traffic_secret_0 +%% | +%% +-----> Derive-Secret(., "s ap traffic", +%% | ClientHello...server Finished) +%% | = server_application_traffic_secret_0 +%% | +%% +-----> Derive-Secret(., "exp master", +%% | ClientHello...server Finished) +%% | = exporter_master_secret +%% | +%% +-----> Derive-Secret(., "res master", +%% ClientHello...client Finished) +%% = resumption_master_secret +-spec key_schedule(early_secret | handshake_secret | master_secret, + atom(), {psk | early_secret | handshake_secret, binary()}) -> + {early_secret | handshake_secret | master_secret, binary()}. + +key_schedule(early_secret, Algo, {psk, PSK}) -> + Len = ssl_cipher:hash_size(Algo), + Salt = binary:copy(<<?BYTE(0)>>, Len), + {early_secret, hkdf_extract(Algo, Salt, PSK)}; +key_schedule(master_secret, Algo, {handshake_secret, Secret}) -> + Len = ssl_cipher:hash_size(Algo), + IKM = binary:copy(<<?BYTE(0)>>, Len), + Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo), + {master_secret, hkdf_extract(Algo, Salt, IKM)}. +%% +key_schedule(handshake_secret, Algo, IKM, {early_secret, Secret}) -> + Salt = derive_secret(Secret, <<"derived">>, <<>>, Algo), + {handshake_secret, hkdf_extract(Algo, Salt, IKM)}. + +-spec external_binder_key(atom(), {early_secret, binary()}) -> binary(). +external_binder_key(Algo, {early_secret, Secret}) -> + derive_secret(Secret, <<"ext binder">>, <<>>, Algo). + +-spec resumption_binder_key(atom(), {early_secret, binary()}) -> binary(). +resumption_binder_key(Algo, {early_secret, Secret}) -> + derive_secret(Secret, <<"res binder">>, <<>>, Algo). + +-spec client_early_traffic_secret(atom(), {early_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello +client_early_traffic_secret(Algo, {early_secret, Secret}, M) -> + derive_secret(Secret, <<"c e traffic">>, M, Algo). + +-spec early_exporter_master_secret(atom(), {early_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello +early_exporter_master_secret(Algo, {early_secret, Secret}, M) -> + derive_secret(Secret, <<"e exp master">>, M, Algo). + +-spec client_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello...ServerHello +client_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) -> + derive_secret(Secret, <<"c hs traffic">>, M, Algo). + +-spec server_handshake_traffic_secret(atom(), {handshake_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello...ServerHello +server_handshake_traffic_secret(Algo, {handshake_secret, Secret}, M) -> + derive_secret(Secret, <<"s hs traffic">>, M, Algo). + +-spec client_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello...server Finished +client_application_traffic_secret_0(Algo, {master_secret, Secret}, M) -> + derive_secret(Secret, <<"c ap traffic">>, M, Algo). + +-spec server_application_traffic_secret_0(atom(), {master_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello...server Finished +server_application_traffic_secret_0(Algo, {master_secret, Secret}, M) -> + derive_secret(Secret, <<"s ap traffic">>, M, Algo). + +-spec exporter_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello...server Finished +exporter_master_secret(Algo, {master_secret, Secret}, M) -> + derive_secret(Secret, <<"exp master">>, M, Algo). + +-spec resumption_master_secret(atom(), {master_secret, binary()}, iodata()) -> binary(). +%% M = ClientHello...client Finished +resumption_master_secret(Algo, {master_secret, Secret}, M) -> + derive_secret(Secret, <<"res master">>, M, Algo). + +%% The next-generation application_traffic_secret is computed as: +%% +%% application_traffic_secret_N+1 = +%% HKDF-Expand-Label(application_traffic_secret_N, +%% "traffic upd", "", Hash.length) +-spec update_traffic_secret(atom(), binary()) -> binary(). +update_traffic_secret(Algo, Secret) -> + hkdf_expand_label(Secret, <<"traffic upd">>, <<>>, ssl_cipher:hash_size(Algo), Algo). + +%% The traffic keying material is generated from the following input +%% values: +%% +%% - A secret value +%% +%% - A purpose value indicating the specific value being generated +%% +%% - The length of the key being generated +%% +%% The traffic keying material is generated from an input traffic secret +%% value using: +%% +%% [sender]_write_key = HKDF-Expand-Label(Secret, "key", "", key_length) +%% [sender]_write_iv = HKDF-Expand-Label(Secret, "iv", "", iv_length) +-spec calculate_traffic_keys(atom(), atom(), binary()) -> {binary(), binary()}. +calculate_traffic_keys(HKDFAlgo, Cipher, Secret) -> + Key = hkdf_expand_label(Secret, <<"key">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo), + IV = hkdf_expand_label(Secret, <<"iv">>, <<>>, ssl_cipher:key_material(Cipher), HKDFAlgo), + {Key, IV}. + +%% TLS v1.3 --------------------------------------------------- + %% TLS 1.0 -1.2 --------------------------------------------------- -spec mac_hash(integer() | atom(), binary(), integer(), integer(), tls_record:tls_version(), integer(), binary()) -> binary(). @@ -254,7 +418,7 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, %% TODO 1.3 same as above? --spec suites(1|2|3|4) -> [ssl_cipher_format:cipher_suite()]. +-spec suites(1|2|3|4|'TLS_v1.3') -> [ssl_cipher_format:cipher_suite()]. suites(Minor) when Minor == 1; Minor == 2 -> [ @@ -315,7 +479,17 @@ suites(4) -> %% Not supported %% ?TLS_AES_128_CCM_SHA256, %% ?TLS_AES_128_CCM_8_SHA256 - ] ++ suites(3). + ] ++ suites(3); + +suites('TLS_v1.3') -> + [?TLS_AES_256_GCM_SHA384, + ?TLS_AES_128_GCM_SHA256, + ?TLS_CHACHA20_POLY1305_SHA256 + %% Not supported + %% ?TLS_AES_128_CCM_SHA256, + %% ?TLS_AES_128_CCM_8_SHA256 + ]. + signature_algs({3, 4}, HashSigns) -> signature_algs({3, 3}, HashSigns); @@ -347,7 +521,9 @@ signature_algs({3, 3}, HashSigns) -> lists:reverse(Supported). default_signature_algs({3, 4} = Version) -> - default_signature_schemes(Version); + %% TLS 1.3 servers shall be prepared to process TLS 1.2 ClientHellos + %% containing legacy hash-sign tuples. + default_signature_schemes(Version) ++ default_signature_algs({3,3}); default_signature_algs({3, 3} = Version) -> Default = [%% SHA2 {sha512, ecdsa}, @@ -373,15 +549,23 @@ signature_schemes(Version, SignatureSchemes) when is_tuple(Version) Hashes = proplists:get_value(hashs, CryptoSupports), PubKeys = proplists:get_value(public_keys, CryptoSupports), Curves = proplists:get_value(curves, CryptoSupports), - Fun = fun (Scheme, Acc) -> + RSAPSSSupported = lists:member(rsa_pkcs1_pss_padding, + proplists:get_value(rsa_opts, CryptoSupports)), + Fun = fun (Scheme, Acc) when is_atom(Scheme) -> {Hash0, Sign0, Curve} = ssl_cipher:scheme_to_components(Scheme), Sign = case Sign0 of - rsa_pkcs1 -> rsa; + rsa_pkcs1 -> + rsa; + rsa_pss_rsae when RSAPSSSupported -> + rsa; + rsa_pss_pss when RSAPSSSupported -> + rsa; S -> S end, Hash = case Hash0 of - sha1 -> sha; + sha1 -> + sha; H -> H end, case proplists:get_bool(Sign, PubKeys) @@ -394,7 +578,10 @@ signature_schemes(Version, SignatureSchemes) when is_tuple(Version) [Scheme | Acc]; false -> Acc - end + end; + %% Special clause for filtering out the legacy hash-sign tuples. + (_ , Acc) -> + Acc end, Supported = lists:foldl(Fun, [], SignatureSchemes), lists:reverse(Supported); @@ -403,22 +590,29 @@ signature_schemes(_, _) -> default_signature_schemes(Version) -> Default = [ - rsa_pkcs1_sha256, - rsa_pkcs1_sha384, - rsa_pkcs1_sha512, - ecdsa_secp256r1_sha256, - ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, - rsa_pss_rsae_sha256, - rsa_pss_rsae_sha384, + ecdsa_secp384r1_sha384, + ecdsa_secp256r1_sha256, + rsa_pss_pss_sha512, + rsa_pss_pss_sha384, + rsa_pss_pss_sha256, rsa_pss_rsae_sha512, + rsa_pss_rsae_sha384, + rsa_pss_rsae_sha256, %% ed25519, %% ed448, - rsa_pss_pss_sha256, - rsa_pss_pss_sha384, - rsa_pss_pss_sha512, - rsa_pkcs1_sha1, - ecdsa_sha1 + + %% These values refer solely to signatures + %% which appear in certificates (see Section 4.4.2.2) and are not + %% defined for use in signed TLS handshake messages, although they + %% MAY appear in "signature_algorithms" and + %% "signature_algorithms_cert" for backward compatibility with + %% TLS 1.2. + rsa_pkcs1_sha512, + rsa_pkcs1_sha384, + rsa_pkcs1_sha256, + ecdsa_sha1, + rsa_pkcs1_sha1 ], signature_schemes(Version, Default). @@ -553,7 +747,9 @@ ecc_curves(_Minor, TLSCurves) -> -spec groups(4 | all | default) -> [group()]. groups(all) -> - [secp256r1, + [x25519, + x448, + secp256r1, secp384r1, secp521r1, ffdhe2048, @@ -562,27 +758,33 @@ groups(all) -> ffdhe6144, ffdhe8192]; groups(default) -> - [secp256r1, - secp384r1, - secp521r1, - ffdhe2048]; + [x25519, + x448, + secp256r1, + secp384r1]; groups(Minor) -> TLSGroups = groups(all), groups(Minor, TLSGroups). %% -spec groups(4, [group()]) -> [group()]. groups(_Minor, TLSGroups) -> - %% TODO: Adding FFDHE groups to crypto? - CryptoGroups = crypto:ec_curves() ++ [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192], + CryptoGroups = supported_groups(), lists:filter(fun(Group) -> proplists:get_bool(Group, CryptoGroups) end, TLSGroups). default_groups(Minor) -> TLSGroups = groups(default), groups(Minor, TLSGroups). +supported_groups() -> + %% TODO: Add new function to crypto? + proplists:get_value(curves, crypto:supports()) ++ + [ffdhe2048,ffdhe3072,ffdhe4096,ffdhe6144,ffdhe8192]. + group_to_enum(secp256r1) -> 23; group_to_enum(secp384r1) -> 24; group_to_enum(secp521r1) -> 25; +group_to_enum(x25519) -> 29; +group_to_enum(x448) -> 30; group_to_enum(ffdhe2048) -> 256; group_to_enum(ffdhe3072) -> 257; group_to_enum(ffdhe4096) -> 258; @@ -592,6 +794,8 @@ group_to_enum(ffdhe8192) -> 260. enum_to_group(23) -> secp256r1; enum_to_group(24) -> secp384r1; enum_to_group(25) -> secp521r1; +enum_to_group(29) -> x25519; +enum_to_group(30) -> x448; enum_to_group(256) -> ffdhe2048; enum_to_group(257) -> ffdhe3072; enum_to_group(258) -> ffdhe4096; diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index 578f6a731a..76bf0fa895 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -189,6 +189,18 @@ gencrl(Root, CA, C, CrlHours) -> Env = [{"ROOTDIR", filename:absname(Root)}], cmd(Cmd, Env). +%% This function sets the number of seconds until the next CRL is due. +gencrl_sec(Root, CA, C, CrlSecs) -> + CACnfFile = filename:join([Root, CA, "ca.cnf"]), + CACRLFile = filename:join([Root, CA, "crl.pem"]), + Cmd = [C#config.openssl_cmd, " ca" + " -gencrl ", + " -crlsec ", integer_to_list(CrlSecs), + " -out ", CACRLFile, + " -config ", CACnfFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + can_generate_expired_crls(C) -> %% OpenSSL can generate CRLs with an expiration date in the past, %% if we pass a negative number for -crlhours. However, LibreSSL diff --git a/lib/ssl/test/property_test/ssl_eqc_handshake.erl b/lib/ssl/test/property_test/ssl_eqc_handshake.erl index 6ffb6d311f..e4c4c89021 100644 --- a/lib/ssl/test/property_test/ssl_eqc_handshake.erl +++ b/lib/ssl/test/property_test/ssl_eqc_handshake.erl @@ -160,7 +160,7 @@ certificate_1_3() -> ?LET(Certs, certificate_chain(), #certificate_1_3{ certificate_request_context = certificate_request_context(), - entries = certificate_entries(Certs, []) + certificate_list = certificate_entries(Certs, []) }). finished() -> @@ -326,7 +326,7 @@ extensions(?'TLS_v1.3' = Version, client_hello) -> %% oneof([psk_key_exchange_modes(), undefined]), %% oneof([early_data(), undefined]), %% oneof([cookie(), undefined]), - oneof([client_hello_versions(Version), undefined]), + oneof([client_hello_versions(Version)]), %% oneof([cert_authorities(), undefined]), %% oneof([post_handshake_auth(), undefined]), oneof([signature_algs_cert(), undefined]) @@ -403,7 +403,7 @@ extensions(?'TLS_v1.3' = Version, server_hello) -> { oneof([key_share(server_hello), undefined]), %% oneof([pre_shared_keys(), undefined]), - oneof([server_hello_selected_version(), undefined]) + oneof([server_hello_selected_version()]) }, maps:filter(fun(_, undefined) -> false; @@ -514,7 +514,9 @@ sig_scheme_list() -> client_hello_versions(?'TLS_v1.3') -> ?LET(SupportedVersions, oneof([[{3,4}], - [{3,3},{3,4}], + %% This list breaks the property but can be used for negative tests + %% [{3,3},{3,4}], + [{3,4},{3,3}], [{3,4},{3,3},{3,2},{3,1},{3,0}] ]), #client_hello_versions{versions = SupportedVersions}); @@ -543,7 +545,10 @@ choose_certificate_chain(#{server_config := ServerConf, oneof([certificate_chain(ServerConf), certificate_chain(ClientConf)]). certificate_request_context() -> - <<>>. + oneof([<<>>, + <<1>>, + <<"foobar">> + ]). certificate_entries([], Acc) -> lists:reverse(Acc); certificate_entries([Cert | Rest], Acc) -> diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec index cb54168d36..24272327c3 100644 --- a/lib/ssl/test/ssl.spec +++ b/lib/ssl/test/ssl.spec @@ -1,4 +1,10 @@ -{suites,"../ssl_test",all}. -{skip_suites, "../ssl_test", - [ssl_bench_SUITE, ssl_dist_bench_SUITE], - "Benchmarks run separately"}. +% {merge_tests,false}. +{alias,dir,"../ssl_test"}. + +{suites,dir,all}. +{skip_groups,dir,ssl_bench_SUITE,setup,"Benchmarks run separately"}. +{skip_groups,dir,ssl_bench_SUITE,payload,"Benchmarks run separately"}. +{skip_groups,dir,ssl_bench_SUITE,pem_cache,"Benchmarks run separately"}. +{skip_groups,dir,ssl_dist_bench_SUITE,setup,"Benchmarks run separately"}. +{skip_groups,dir,ssl_dist_bench_SUITE,throughput,"Benchmarks run separately"}. + diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl index 04c4b257d9..7f7c3da5ab 100644 --- a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl @@ -262,52 +262,12 @@ client_renegotiate(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- session_reused(Config) when is_list(Config)-> - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0, ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result_msg, []}}, - {options, ClientOpts}]), - - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - - receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:fail(Other) - end, - - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). %-------------------------------------------------------------------------------- alpn_not_supported_client(Config) when is_list(Config) -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 1cfff436d2..0727505dde 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -53,7 +53,8 @@ all() -> {group, options_tls}, {group, session}, {group, 'dtlsv1.2'}, - {group, 'dtlsv1'}, + {group, 'dtlsv1'}, + {group, 'tlsv1.3'}, {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, @@ -67,6 +68,7 @@ groups() -> {options_tls, [], options_tests_tls()}, {'dtlsv1.2', [], all_versions_groups()}, {'dtlsv1', [], all_versions_groups()}, + {'tlsv1.3', [], tls13_test_group()}, {'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]}, {'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()}, {'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()}, @@ -266,6 +268,12 @@ rizzo_tests() -> rizzo_zero_n, rizzo_disabled]. +%% For testing TLS 1.3 features and possible regressions +tls13_test_group() -> + [tls13_enable_client_side, + tls13_enable_server_side, + tls_record_1_3_encode_decode]. + %%-------------------------------------------------------------------- init_per_suite(Config0) -> catch crypto:stop(), @@ -295,7 +303,8 @@ init_per_group(GroupName, Config) when GroupName == basic_tls; GroupName == options; GroupName == basic; GroupName == session; - GroupName == error_handling_tests_tls + GroupName == error_handling_tests_tls; + GroupName == tls13_test_group -> ssl_test_lib:clean_tls_version(Config); init_per_group(GroupName, Config) -> @@ -654,8 +663,8 @@ new_options_in_accept(Config) when is_list(Config) -> handshake_continue() -> [{doc, "Test API function ssl:handshake_continue/3"}]. handshake_continue(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), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -714,7 +723,7 @@ hello_client_cancel(Config) when is_list(Config) -> hello_server_cancel() -> [{doc, "Test API function ssl:handshake_cancel/1 on the server side"}]. hello_server_cancel(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -756,8 +765,8 @@ prf(Config) when is_list(Config) -> secret_connection_info() -> [{doc,"Test the API function ssl:connection_information/2"}]. secret_connection_info(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), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -838,42 +847,30 @@ controlling_process(Config) when is_list(Config) -> ClientMsg = "Server hello", ServerMsg = "Client hello", - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {?MODULE, - controlling_process_result, [self(), - ServerMsg]}}, - {options, ServerOpts}]), + Server = ssl_test_lib:start_server([ + {node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, + controlling_process_result, [self(), + ServerMsg]}}, + {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, + {Client, CSocket} = ssl_test_lib:start_client([return_socket, + {node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, {mfa, {?MODULE, controlling_process_result, [self(), ClientMsg]}}, {options, ClientOpts}]), - + ct:log("Testcase ~p, Client ~p Server ~p ~n", - [self(), Client, Server]), + [self(), Client, Server]), - receive - {ssl, _, "S"} -> - receive_s_rizzo_duong_beast(); - {ssl, _, ServerMsg} -> - receive - {ssl, _, ClientMsg} -> - ok - end; - {ssl, _, "C"} -> - receive_c_rizzo_duong_beast(); - {ssl, _, ClientMsg} -> - receive - {ssl, _, ServerMsg} -> - ok - end; - Unexpected -> - ct:fail(Unexpected) - end, + ServerMsg = ssl_test_lib:active_recv(CSocket, length(ServerMsg)), + %% We do not have the TLS server socket but all messages form the client + %% socket are now read, so ramining are form the server socket + ClientMsg = ssl_active_recv(length(ClientMsg)), ssl_test_lib:close(Server), ssl_test_lib:close(Client). @@ -1097,16 +1094,19 @@ tls_closed_in_active_once(Config) when is_list(Config) -> end. tls_closed_in_active_once_loop(Socket) -> - ssl:setopts(Socket, [{active, once}]), - receive - {ssl, Socket, _} -> - tls_closed_in_active_once_loop(Socket); - {ssl_closed, Socket} -> - ok - after 5000 -> - no_ssl_closed_received + case ssl:setopts(Socket, [{active, once}]) of + ok -> + receive + {ssl, Socket, _} -> + tls_closed_in_active_once_loop(Socket); + {ssl_closed, Socket} -> + ok + after 5000 -> + no_ssl_closed_received + end; + {error, closed} -> + ok end. - %%-------------------------------------------------------------------- connect_dist() -> [{doc,"Test a simple connect as is used by distribution"}]. @@ -1455,8 +1455,8 @@ cipher_suites_mix() -> cipher_suites_mix(Config) when is_list(Config) -> CipherSuites = [{dhe_rsa,aes_128_cbc,sha256,sha256}, {dhe_rsa,aes_128_cbc,sha}], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2115,15 +2115,21 @@ tls_downgrade(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, []}}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, {options, [{active, false} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, tls_downgrade_result, []}}, + {mfa, {?MODULE, tls_downgrade_result, [self()]}}, {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ready, Client, ready), + + Server ! go, + Client ! go, + ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). @@ -2361,8 +2367,8 @@ invalid_options() -> [{doc,"Test what happens when we give invalid options"}]. invalid_options(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_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> @@ -2377,27 +2383,28 @@ invalid_options(Config) when is_list(Config) -> {error, {options, Option}}) end, - TestOpts = [{versions, [sslv2, sslv3]}, - {verify, 4}, - {verify_fun, function}, - {fail_if_no_peer_cert, 0}, - {verify_client_once, 1}, - {depth, four}, - {certfile, 'cert.pem'}, - {keyfile,'key.pem' }, - {password, foo}, - {cacertfile, ""}, - {dhfile,'dh.pem' }, - {ciphers, [{foo, bar, sha, ignore}]}, - {reuse_session, foo}, - {reuse_sessions, 0}, - {renegotiate_at, "10"}, - {mode, depech}, - {packet, 8.0}, - {packet_size, "2"}, - {header, a}, - {active, trice}, - {key, 'key.pem' }], + TestOpts = + [{versions, [sslv2, sslv3]}, + {verify, 4}, + {verify_fun, function}, + {fail_if_no_peer_cert, 0}, + {verify_client_once, 1}, + {depth, four}, + {certfile, 'cert.pem'}, + {keyfile,'key.pem' }, + {password, foo}, + {cacertfile, ""}, + {dhfile,'dh.pem' }, + {ciphers, [{foo, bar, sha, ignore}]}, + {reuse_session, foo}, + {reuse_sessions, 0}, + {renegotiate_at, "10"}, + {mode, depech}, + {packet, 8.0}, + {packet_size, "2"}, + {header, a}, + {active, trice}, + {key, 'key.pem' }], [begin Server = @@ -2690,175 +2697,69 @@ ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> reuse_session() -> [{doc,"Test reuse of sessions (short handshake)"}]. reuse_session(Config) when is_list(Config) -> - 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, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:log("Expected: ~p, Unexpected: ~p~n", - [SessionInfo, Other]), - ct:fail(session_not_reused) - end, + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - Client2 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, [{reuse_sessions, false} - | ClientOpts]}]), - receive - {Client2, SessionInfo} -> - ct:fail( - session_reused_when_session_reuse_disabled_by_client); - {Client2, _} -> - ok - end, - - ssl_test_lib:close(Server), - - Server1 = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, [{reuse_sessions, false} | ServerOpts]}]), - - Port1 = ssl_test_lib:inet_port(Server1), - Client3 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - - SessionInfo1 = - receive - {Server1, Info1} -> - Info1 - end, - - Server1 ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client4 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port1}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - - receive - {Client4, SessionInfo1} -> - ct:fail( - session_reused_when_session_reuse_disabled_by_server); - {Client4, _Other} -> - ct:log("OTHER: ~p ~n", [_Other]), - ok - end, - - ssl_test_lib:close(Server1), - ssl_test_lib:close(Client0), - ssl_test_lib:close(Client1), - ssl_test_lib:close(Client2), - ssl_test_lib:close(Client3), - ssl_test_lib:close(Client4). - + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). %%-------------------------------------------------------------------- reuse_session_expired() -> [{doc,"Test sessions is not reused when it has expired"}]. reuse_session_expired(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_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - - Server = - ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - Client0 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + Server0 ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:log("Expected: ~p, Unexpected: ~p~n", - [SessionInfo, Other]), - ct:fail(session_not_reused) + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) end, - Server ! listen, - + Server0 ! listen, + %% Make sure session is unregistered due to expiration - ct:sleep((?EXPIRE+1)), - [{session_id, Id} |_] = SessionInfo, + ct:sleep((?EXPIRE*2)), - make_sure_expired(Hostname, Port, Id), + make_sure_expired(Hostname, Port0, SID), Client2 = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, {from, self()}, {options, ClientOpts}]), receive - {Client2, SessionInfo} -> + {Client2, SID} -> ct:fail(session_reused_when_session_expired); {Client2, _} -> ok end, process_flag(trap_exit, false), - ssl_test_lib:close(Server), + ssl_test_lib:close(Server0), ssl_test_lib:close(Client0), ssl_test_lib:close(Client1), ssl_test_lib:close(Client2). @@ -2867,16 +2768,16 @@ make_sure_expired(Host, Port, Id) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Cache = element(2, State), + ClientCache = element(2, State), - case ssl_session_cache:lookup(Cache, {{Host, Port}, Id}) of + case ssl_session_cache:lookup(ClientCache, {{Host, Port}, Id}) of undefined -> - ok; + ok; #session{is_resumable = false} -> - ok; + ok; _ -> ct:sleep(?SLEEP), - make_sure_expired(Host, Port, Id) + make_sure_expired(Host, Port, Id) end. %%-------------------------------------------------------------------- @@ -3977,8 +3878,8 @@ tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(Pid), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Socket = element(11, State), - + StaticEnv = element(2, State), + Socket = element(10, StaticEnv), %% Fake tcp error Pid ! {tcp_error, Socket, etimedout}, @@ -4128,6 +4029,8 @@ rizzo(Config) when is_list(Config) -> {cipher, fun(rc4_128) -> false; + (chacha20_poly1305) -> + false; (_) -> true end}]), @@ -4473,6 +4376,163 @@ accept_pool(Config) when is_list(Config) -> ssl_test_lib:close(Client1), ssl_test_lib:close(Client2). +%%-------------------------------------------------------------------- +%% TLS 1.3 +%%-------------------------------------------------------------------- + +tls13_enable_client_side() -> + [{doc,"Test that a TLS 1.3 client can connect to a TLS 1.2 server."}]. + +tls13_enable_client_side(Config) when is_list(Config) -> + 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, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, + ['tlsv1.1', 'tlsv1.2']} | ServerOpts] }]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, + ['tlsv1.2', 'tlsv1.3']} | ClientOpts]}]), + + ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +tls13_enable_server_side() -> + [{doc,"Test that a TLS 1.2 client can connect to a TLS 1.3 server."}]. + +tls13_enable_server_side(Config) when is_list(Config) -> + 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, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, + ['tlsv1.2', 'tlsv1.3']} | ServerOpts] }]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, protocol_info_result, []}}, + {options, [{versions, + ['tlsv1.2', 'tlsv1.1']} | ClientOpts]}]), + + ServerMsg = ClientMsg = {ok, 'tlsv1.2'}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +tls_record_1_3_encode_decode() -> + [{doc,"Test TLS 1.3 record encode/decode functions"}]. + +tls_record_1_3_encode_decode(_Config) -> + ConnectionStates = + #{current_read => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}, + current_write => + #{beast_mitigation => one_n_minus_one, + cipher_state => + {cipher_state, + <<14,172,111,243,199,170,242,203,126,205,34,93,122,115,226,14, + 15,117,155,48,24,112,61,15,113,208,127,51,179,227,194,232>>, + <<197,54,168,218,54,91,157,58,30,201,197,142,51,58,53,231,228, + 131,57,122,170,78,82,196,30,48,23,16,95,255,185,236>>, + undefined,undefined,16}, + client_verify_data => undefined,compression_state => undefined, + mac_secret => undefined,secure_renegotiation => undefined, + security_parameters => + {security_parameters, + <<19,2>>, + 0,8,2,undefined,undefined,undefined,undefined,undefined, + sha384,undefined,undefined, + {handshake_secret, + <<128,229,186,211,62,127,182,20,62,166,233,23,135,64,121, + 3,104,251,214,161,253,31,3,2,232,37,8,221,189,72,64,218, + 121,41,112,148,254,34,68,164,228,60,161,201,132,55,56, + 157>>}, + undefined, + <<92,24,205,75,244,60,136,212,250,32,214,20,37,3,213,87,61,207, + 147,61,168,145,177,118,160,153,33,53,48,108,191,174>>, + undefined}, + sequence_number => 0,server_verify_data => undefined}}, + + PlainText = [11, + <<0,2,175>>, + <<0,0,2,171,0,2,166,48,130,2,162,48,130,1,138,2,9,0,186,57,220,137,88,255, + 191,235,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,48,18,49,16,48,14,6,3,85, + 4,3,12,7,84,101,115,116,32,67,65,48,30,23,13,49,56,48,53,48,52,49,52,49,50, + 51,56,90,23,13,50,56,48,50,48,52,49,52,49,50,51,56,90,48,20,49,18,48,16,6, + 3,85,4,3,12,9,108,111,99,97,108,104,111,115,116,48,130,1,34,48,13,6,9,42, + 134,72,134,247,13,1,1,1,5,0,3,130,1,15,0,48,130,1,10,2,130,1,1,0,169,40, + 144,176,121,63,134,97,144,126,243,183,225,157,37,131,183,225,87,243,23,88, + 230,70,9,134,32,147,7,27,167,98,51,81,224,75,199,12,229,251,195,207,75,179, + 181,78,128,3,255,44,58,39,43,172,142,45,186,58,51,65,187,199,154,153,245, + 70,133,137,1,27,87,42,116,65,251,129,109,145,233,97,171,71,54,213,185,74, + 209,166,11,218,189,119,206,86,170,60,212,213,85,189,30,50,215,23,185,53, + 132,238,132,176,198,250,139,251,198,221,225,128,109,113,23,220,39,143,71, + 30,59,189,51,244,61,158,214,146,180,196,103,169,189,221,136,78,129,216,148, + 2,9,8,65,37,224,215,233,13,209,21,235,20,143,33,74,59,53,208,90,152,94,251, + 54,114,171,39,88,230,227,158,211,135,37,182,67,205,161,59,20,138,58,253,15, + 53,48,8,157,9,95,197,9,177,116,21,54,9,125,78,109,182,83,20,16,234,223,116, + 41,155,123,87,77,17,120,153,246,239,124,130,105,219,166,146,242,151,66,198, + 75,72,63,28,246,86,16,244,223,22,36,50,15,247,222,98,6,152,136,154,72,150, + 73,127,2,3,1,0,1,48,13,6,9,42,134,72,134,247,13,1,1,11,5,0,3,130,1,1,0,76, + 33,54,160,229,219,219,193,150,116,245,252,18,39,235,145,86,12,167,171,52, + 117,166,30,83,5,216,245,177,217,247,95,1,136,94,246,212,108,248,230,111, + 225,202,189,6,129,8,70,128,245,18,204,215,87,82,129,253,227,122,66,182,184, + 189,30,193,169,144,218,216,109,105,110,215,144,60,104,162,178,101,164,218, + 122,60,37,41,143,57,150,52,59,51,112,238,113,239,168,114,69,183,143,154,73, + 61,58,80,247,172,95,251,55,28,186,28,200,206,230,118,243,92,202,189,49,76, + 124,252,76,0,247,112,85,194,69,59,222,163,228,103,49,110,104,109,251,155, + 138,9,37,167,49,189,48,134,52,158,185,129,24,96,153,196,251,90,206,76,239, + 175,119,174,165,133,108,222,125,237,125,187,149,152,83,190,16,202,94,202, + 201,40,218,22,254,63,189,41,174,97,140,203,70,18,196,118,237,175,134,79,78, + 246,2,61,54,77,186,112,32,17,193,192,188,217,252,215,200,7,245,180,179,132, + 183,212,229,155,15,152,206,135,56,81,88,3,123,244,149,110,182,72,109,70,62, + 146,152,146,151,107,126,216,210,9,93,0,0>>], + + {[_Header|Encoded], _} = tls_record_1_3:encode_plain_text(22, PlainText, ConnectionStates), + CipherText = #ssl_tls{type = 23, version = {3,3}, fragment = Encoded}, + + {#ssl_tls{type = 22, version = {3,4}, fragment = DecodedText}, _} = + tls_record_1_3:decode_cipher_text(CipherText, ConnectionStates), + + DecodedText = iolist_to_binary(PlainText), + ct:log("Decoded: ~p ~n", [DecodedText]), + ok. + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ @@ -4487,8 +4547,8 @@ tcp_send_recv_result(Socket) -> ok. basic_verify_test_no_close(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -4631,19 +4691,24 @@ recv_close(Socket) -> send_recv_result_active_rizzo(Socket) -> ssl:send(Socket, "Hello world"), - receive - {ssl, Socket, "H"} -> - receive - {ssl, Socket, "ello world"} -> - ok - end - end. + "Hello world" = ssl_test_lib:active_recv(Socket, 11), + ok. send_recv_result_active_no_rizzo(Socket) -> ssl:send(Socket, "Hello world"), + "Hello world" = ssl_test_lib:active_recv(Socket, 11), + ok. + + +ssl_active_recv(N) -> + ssl_active_recv(N, []). + +ssl_active_recv(0, Acc) -> + Acc; +ssl_active_recv(N, Acc) -> receive - {ssl, Socket, "Hello world"} -> - ok + {ssl, _, Bytes} -> + ssl_active_recv(N-length(Bytes), Acc ++ Bytes) end. result_ok(_Socket) -> @@ -4667,16 +4732,7 @@ renegotiate_reuse_session(Socket, Data) -> renegotiate(Socket, Data). renegotiate_immediately(Socket) -> - receive - {ssl, Socket, "Hello world"} -> - ok; - %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast - {ssl, Socket, "H"} -> - receive - {ssl, Socket, "ello world"} -> - ok - end - end, + _ = ssl_test_lib:active_recv(Socket, 11), ok = ssl:renegotiate(Socket), {error, renegotiation_rejected} = ssl:renegotiate(Socket), ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), @@ -4686,17 +4742,7 @@ renegotiate_immediately(Socket) -> ok. renegotiate_rejected(Socket) -> - receive - {ssl, Socket, "Hello world"} -> - ok; - %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast - {ssl, Socket, "H"} -> - - receive - {ssl, Socket, "ello world"} -> - ok - end - end, + _ = ssl_test_lib:active_recv(Socket, 11), {error, renegotiation_rejected} = ssl:renegotiate(Socket), {error, renegotiation_rejected} = ssl:renegotiate(Socket), ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), @@ -4871,17 +4917,11 @@ session_loop(Sess) -> erlang_ssl_receive(Socket, Data) -> - receive - {ssl, Socket, Data} -> - io:format("Received ~p~n",[Data]), - ok; - {ssl, Socket, Byte} when length(Byte) == 1 -> %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast - io:format("Received ~p~n",[Byte]), - erlang_ssl_receive(Socket, tl(Data)); - Other -> - ct:fail({unexpected_message, Other}) - after timer:seconds(?SEC_RENEGOTIATION_TIMEOUT) * test_server:timetrap_scale_factor() -> - ct:fail({did_not_get, Data}) + case ssl_test_lib:active_recv(Socket, length(Data)) of + Data -> + ok; + Other -> + ct:fail({{expected, Data}, {got, Other}}) end. receive_msg(_) -> @@ -4898,28 +4938,6 @@ controlling_process_result(Socket, Pid, Msg) -> ssl:send(Socket, Msg), no_result_msg. -receive_s_rizzo_duong_beast() -> - receive - {ssl, _, "erver hello"} -> - receive - {ssl, _, "C"} -> - receive - {ssl, _, "lient hello"} -> - ok - end - end - end. -receive_c_rizzo_duong_beast() -> - receive - {ssl, _, "lient hello"} -> - receive - {ssl, _, "S"} -> - receive - {ssl, _, "erver hello"} -> - ok - end - end - end. controller_dies_result(_Socket, _Pid, _Msg) -> receive Result -> Result end. @@ -5005,16 +5023,16 @@ run_suites(Ciphers, Config, Type) -> {ClientOpts, ServerOpts} = case Type of rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_opts, Config)]}; dsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_dsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_dsa_opts, Config)]}; anonymous -> %% No certs in opts! - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options([], Config)]}; psk -> @@ -5036,46 +5054,50 @@ run_suites(Ciphers, Config, Type) -> ssl_test_lib:ssl_options(server_psk_anon_hint, Config)]}; srp -> {ssl_test_lib:ssl_options(client_srp, Config), - ssl_test_lib:ssl_options(server_srp, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_srp, Config)]}; srp_anon -> {ssl_test_lib:ssl_options(client_srp, Config), - ssl_test_lib:ssl_options(server_srp_anon, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_srp_anon, Config)]}; srp_dsa -> {ssl_test_lib:ssl_options(client_srp_dsa, Config), - ssl_test_lib:ssl_options(server_srp_dsa, Config)}; + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_srp_dsa, Config)]}; ecdsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_ecdsa_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; ecdh_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), - ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}; + {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; rc4_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; rc4_ecdh_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_ecdh_rsa_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; rc4_ecdsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; des_dhe_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_verification_opts, Config)]}; des_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; chacha_rsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_verification_opts, Config)]}; + ssl_test_lib:ssl_options(server_rsa_verify_opts, Config)]}; chacha_ecdsa -> - {ssl_test_lib:ssl_options(client_verification_opts, Config), + {ssl_test_lib:ssl_options(client_ecdsa_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]} end, @@ -5173,23 +5195,28 @@ connect_dist_c(S) -> {ok, Test} = ssl:recv(S, 0, 10000), ok. -tls_downgrade_result(Socket) -> +tls_downgrade_result(Socket, Pid) -> ok = ssl_test_lib:send_recv_result(Socket), + Pid ! {self(), ready}, + receive + go -> + ok + end, case ssl:close(Socket, {self(), 10000}) of {ok, TCPSocket} -> - inet:setopts(TCPSocket, [{active, true}]), + inet:setopts(TCPSocket, [{active, true}]), gen_tcp:send(TCPSocket, "Downgraded"), - receive - {tcp, TCPSocket, <<"Downgraded">>} -> - ok; - {tcp_closed, TCPSocket} -> - ct:pal("Peer timed out, downgrade aborted"), - ok; - Other -> - {error, Other} - end; + receive + {tcp, TCPSocket, <<"Downgraded">>} -> + ok; + {tcp_closed, TCPSocket} -> + ct:fail("Peer timed out, downgrade aborted"), + ok; + Other -> + {error, Other} + end; {error, timeout} -> - ct:pal("Timed out, downgrade aborted"), + ct:fail("Timed out, downgrade aborted"), ok; Fail -> {error, Fail} @@ -5220,14 +5247,14 @@ get_invalid_inet_option(Socket) -> tls_shutdown_result(Socket, server) -> ssl:send(Socket, "Hej"), - ssl:shutdown(Socket, write), + ok = ssl:shutdown(Socket, write), {ok, "Hej hopp"} = ssl:recv(Socket, 8), ok; tls_shutdown_result(Socket, client) -> - {ok, "Hej"} = ssl:recv(Socket, 3), ssl:send(Socket, "Hej hopp"), - ssl:shutdown(Socket, write), + ok = ssl:shutdown(Socket, write), + {ok, "Hej"} = ssl:recv(Socket, 3), ok. tls_shutdown_write_result(Socket, server) -> diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec index 8b746c5ca9..217cc6fc83 100644 --- a/lib/ssl/test/ssl_bench.spec +++ b/lib/ssl/test/ssl_bench.spec @@ -1 +1,2 @@ {suites,"../ssl_test",[ssl_bench_SUITE, ssl_dist_bench_SUITE]}. +{skip_groups,"../ssl_test",ssl_bench_SUITE,basic,"Benchmarks run separately"}. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl index 13097b08b6..0b2011a627 100644 --- a/lib/ssl/test/ssl_bench_SUITE.erl +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -25,10 +25,11 @@ suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. -all() -> [{group, setup}, {group, payload}, {group, pem_cache}]. +all() -> [{group, basic}, {group, setup}, {group, payload}, {group, pem_cache}]. groups() -> - [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, + [{basic, [], [basic_pem_cache]}, + {setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, {payload, [{repeat, 3}], [payload_simple]}, {pem_cache, [{repeat, 3}], [use_pem_cache, bypass_pem_cache]} ]. @@ -51,29 +52,21 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_testcase(use_pem_cache, Conf) -> +init_per_testcase(TC, Conf) when TC =:= use_pem_cache; + TC =:= bypass_pem_cache; + TC =:= basic_pem_cache -> 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) -> +end_per_testcase(TC, _Config) when TC =:= use_pem_cache; + TC =:= bypass_pem_cache; + TC =:= basic_pem_cache -> case bypass_pem_cache_supported() of false -> ok; true -> application:set_env(ssl, bypass_pem_cache, false) @@ -119,6 +112,9 @@ payload_simple(Config) -> {suite, "ssl"}, {name, "Payload simple"}]}), ok. +basic_pem_cache(_Config) -> + do_test(ssl, pem_cache, 10, 5, node()). + use_pem_cache(_Config) -> {ok, Result} = do_test(ssl, pem_cache, 100, 500, node()), ct_event:notify(#event{name = benchmark_data, diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl index 23c5eaf84d..c61039b5da 100644 --- a/lib/ssl/test/ssl_crl_SUITE.erl +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -383,8 +383,11 @@ crl_hash_dir_expired(Config) when is_list(Config) -> {verify, verify_peer}], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - %% First make a CRL that expired yesterday. - make_certs:gencrl(PrivDir, CA, CertsConfig, -24), + %% First make a CRL that will expire in one second. + make_certs:gencrl_sec(PrivDir, CA, CertsConfig, 1), + %% Sleep until the next CRL is due + ct:sleep({seconds, 1}), + CrlDir = filename:join(PrivDir, "crls"), populate_crl_hash_dir(PrivDir, CrlDir, [{CA, "1627b4b0"}], diff --git a/lib/ssl/test/ssl_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl index 3c7904cf24..d9d9c4e473 100644 --- a/lib/ssl/test/ssl_dist_bench_SUITE.erl +++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl @@ -32,6 +32,9 @@ -export( [setup/1, roundtrip/1, + sched_utilization/1, + throughput_0/1, + throughput_64/1, throughput_1024/1, throughput_4096/1, throughput_16384/1, @@ -54,8 +57,11 @@ groups() -> %% {setup, [{repeat, 1}], [setup]}, {roundtrip, [{repeat, 1}], [roundtrip]}, + {sched_utilization,[{repeat, 1}], [sched_utilization]}, {throughput, [{repeat, 1}], - [throughput_1024, + [throughput_0, + throughput_64, + throughput_1024, throughput_4096, throughput_16384, throughput_65536, @@ -65,7 +71,9 @@ groups() -> all_groups() -> [{group, setup}, {group, roundtrip}, - {group, throughput}]. + {group, throughput}, + {group, sched_utilization} + ]. init_per_suite(Config) -> Digest = sha1, @@ -247,8 +255,9 @@ setup(A, B, Prefix, HA, HB) -> [] = ssl_apply(HB, erlang, nodes, []), {SetupTime, CycleTime} = ssl_apply(HA, fun () -> setup_runner(A, B, Rounds) end), - [] = ssl_apply(HA, erlang, nodes, []), - [] = ssl_apply(HB, erlang, nodes, []), + ok = ssl_apply(HB, fun () -> setup_wait_nodedown(A, 10000) end), + %% [] = ssl_apply(HA, erlang, nodes, []), + %% [] = ssl_apply(HB, erlang, nodes, []), SetupSpeed = round((Rounds*1000000*1000) / SetupTime), CycleSpeed = round((Rounds*1000000*1000) / CycleTime), _ = report(Prefix++" Setup", SetupSpeed, "setups/1000s"), @@ -275,6 +284,22 @@ setup_loop(A, B, T, N) -> setup_loop(A, B, Time + T, N - 1) end. +setup_wait_nodedown(A, Time) -> + ok = net_kernel:monitor_nodes(true), + case nodes() of + [] -> + ok; + [A] -> + receive + {nodedown,A} -> + ok; + Unexpected -> + {error,{unexpected,Unexpected}} + after Time -> + {error,timeout} + end + end. + %%---------------- %% Roundtrip speed @@ -330,10 +355,115 @@ roundtrip_client(Pid, Mon, StartTime, N) -> roundtrip_client(Pid, Mon, StartTime, N - 1) end. +%%--------------------------------------- +%% Scheduler utilization at constant load + + +sched_utilization(Config) -> + run_nodepair_test( + fun(A, B, Prefix, HA, HB) -> + sched_utilization(A, B, Prefix, HA, HB, proplists:get_value(ssl_dist, Config)) + end, Config). + +sched_utilization(A, B, Prefix, HA, HB, SSL) -> + [] = ssl_apply(HA, erlang, nodes, []), + [] = ssl_apply(HB, erlang, nodes, []), + {ClientMsacc, ServerMsacc, Msgs} = + ssl_apply(HA, fun () -> sched_util_runner(A, B, SSL) end), + [B] = ssl_apply(HA, erlang, nodes, []), + [A] = ssl_apply(HB, erlang, nodes, []), + msacc:print(ClientMsacc), + msacc:print(ServerMsacc), + ct:pal("Got ~p msgs",[length(Msgs)]), + report(Prefix++" Sched Utilization Client", + 10000 * msacc:stats(system_runtime,ClientMsacc) / + msacc:stats(system_realtime,ClientMsacc), "util 0.01 %"), + report(Prefix++" Sched Utilization Server", + 10000 * msacc:stats(system_runtime,ServerMsacc) / + msacc:stats(system_realtime,ServerMsacc), "util 0.01 %"), + ok. + +%% Runs on node A and spawns a server on node B +%% We want to avoid getting busy_dist_port as it hides the true SU usage +%% of the receiver and sender. +sched_util_runner(A, B, true) -> + sched_util_runner(A, B, 50); +sched_util_runner(A, B, false) -> + sched_util_runner(A, B, 250); +sched_util_runner(A, B, Senders) -> + Payload = payload(5), + [A] = rpc:call(B, erlang, nodes, []), + ServerPid = + erlang:spawn( + B, + fun () -> throughput_server() end), + ServerMsacc = + erlang:spawn( + B, + fun() -> + receive + {start,Pid} -> + msacc:start(10000), + Pid ! {ServerPid,msacc:stats()} + end + end), + spawn_link( + fun() -> + %% We spawn 250 senders which should mean that we + %% have a load of 250 msgs/msec + [spawn_link( + fun() -> + throughput_client(ServerPid,Payload) + end) || _ <- lists:seq(1, Senders)] + end), + + erlang:system_monitor(self(),[busy_dist_port]), + ServerMsacc ! {start,self()}, + msacc:start(10000), + ClientMsaccStats = msacc:stats(), + ServerMsaccStats = receive {ServerPid,Stats} -> Stats end, + {ClientMsaccStats,ServerMsaccStats, flush()}. + +flush() -> + receive + M -> + [M | flush()] + after 0 -> + [] + end. + +throughput_server() -> + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + receive _ -> ok end, + throughput_server(). + +throughput_client(Pid, Payload) -> + Pid ! Payload, + receive after 1 -> throughput_client(Pid, Payload) end. %%----------------- %% Throughput speed +throughput_0(Config) -> + run_nodepair_test( + fun (A, B, Prefix, HA, HB) -> + throughput(A, B, Prefix, HA, HB, 500000, 0) + end, Config). + +throughput_64(Config) -> + run_nodepair_test( + fun (A, B, Prefix, HA, HB) -> + throughput(A, B, Prefix, HA, HB, 500000, 64) + end, Config). + throughput_1024(Config) -> run_nodepair_test( fun (A, B, Prefix, HA, HB) -> @@ -373,45 +503,198 @@ throughput_1048576(Config) -> throughput(A, B, Prefix, HA, HB, Packets, Size) -> [] = ssl_apply(HA, erlang, nodes, []), [] = ssl_apply(HB, erlang, nodes, []), - Time = + #{time := Time, + dist_stats := DistStats, + client_msacc_stats := ClientMsaccStats, + client_prof := ClientProf, + server_msacc_stats := ServerMsaccStats, + server_prof := ServerProf} = ssl_apply(HA, fun () -> throughput_runner(A, B, Packets, Size) end), [B] = ssl_apply(HA, erlang, nodes, []), [A] = ssl_apply(HB, erlang, nodes, []), - Speed = round((Packets*Size*1000000) / (1024*Time)), + ClientMsaccStats =:= undefined orelse + msacc:print(ClientMsaccStats), + io:format("DistStats: ~p~n", [DistStats]), + Overhead = + 50 % Distribution protocol headers (empirical) (TLS+=54) + + byte_size(erlang:term_to_binary([0|<<>>])), % Benchmark overhead + Bytes = Packets * (Size + Overhead), + io:format("~w bytes, ~.4g s~n", [Bytes,Time/1000000]), + ClientMsaccStats =:= undefined orelse + io:format( + "Sender core usage ratio: ~.4g ns/byte~n", + [msacc:stats(system_runtime, ClientMsaccStats)*1000/Bytes]), + ServerMsaccStats =:= undefined orelse + begin + io:format( + "Receiver core usage ratio: ~.4g ns/byte~n", + [msacc:stats(system_runtime, ServerMsaccStats)*1000/Bytes]), + msacc:print(ServerMsaccStats) + end, + io:format("******* ClientProf:~n", []), prof_print(ClientProf), + io:format("******* ServerProf:~n", []), prof_print(ServerProf), + Speed = round((Bytes * 1000000) / (1024 * Time)), report(Prefix++" Throughput_"++integer_to_list(Size), Speed, "kB/s"). %% Runs on node A and spawns a server on node B throughput_runner(A, B, Rounds, Size) -> Payload = payload(Size), - ClientPid = self(), [A] = rpc:call(B, erlang, nodes, []), + ClientPid = self(), ServerPid = erlang:spawn( B, fun () -> throughput_server(ClientPid, Rounds) end), ServerMon = erlang:monitor(process, ServerPid), - microseconds( - throughput_client( - ServerPid, ServerMon, Payload, start_time(), Rounds)). + msacc:available() andalso + begin + msacc:stop(), + msacc:reset(), + msacc:start(), + ok + end, + prof_start(), + {Time,ServerMsaccStats,ServerProf} = + throughput_client(ServerPid, ServerMon, Payload, Rounds), + prof_stop(), + ClientMsaccStats = + case msacc:available() of + true -> + MStats = msacc:stats(), + msacc:stop(), + MStats; + false -> + undefined + end, + ClientProf = prof_end(), + [{_Node,Socket}] = dig_dist_node_sockets(), + DistStats = inet:getstat(Socket), + #{time => microseconds(Time), + dist_stats => DistStats, + client_msacc_stats => ClientMsaccStats, + client_prof => ClientProf, + server_msacc_stats => ServerMsaccStats, + server_prof => ServerProf}. + +dig_dist_node_sockets() -> + [case DistCtrl of + {_Node,Socket} = NodeSocket when is_port(Socket) -> + NodeSocket; + {Node,DistCtrlPid} when is_pid(DistCtrlPid) -> + [{links,DistCtrlLinks}] = process_info(DistCtrlPid, [links]), + case [S || S <- DistCtrlLinks, is_port(S)] of + [Socket] -> + {Node,Socket}; + [] -> + [{monitors,[{process,DistSenderPid}]}] = + process_info(DistCtrlPid, [monitors]), + [{links,DistSenderLinks}] = + process_info(DistSenderPid, [links]), + [Socket] = [S || S <- DistSenderLinks, is_port(S)], + {Node,Socket} + end + end || DistCtrl <- erlang:system_info(dist_ctrl)]. + -throughput_server(_Pid, 0) -> - ok; throughput_server(Pid, N) -> + msacc:available() andalso + begin + msacc:stop(), + msacc:reset(), + msacc:start(), + ok + end, + prof_start(), + throughput_server_loop(Pid, N). + +throughput_server_loop(_Pid, 0) -> + prof_stop(), + MsaccStats = + case msacc:available() of + true -> + msacc:stop(), + MStats = msacc:stats(), + msacc:reset(), + MStats; + false -> + undefined + end, + Prof = prof_end(), + exit({ok,MsaccStats,Prof}); +throughput_server_loop(Pid, N) -> receive - [N|_] -> - throughput_server(Pid, N-1) + {Pid, N, _} -> + throughput_server_loop(Pid, N-1) end. -throughput_client(_Pid, Mon, _Payload, StartTime, 0) -> +throughput_client(Pid, Mon, Payload, N) -> + throughput_client_loop(Pid, Mon, Payload, N, start_time()). + +throughput_client_loop(_Pid, Mon, _Payload, 0, StartTime) -> receive - {'DOWN', Mon, _, _, normal} -> - elapsed_time(StartTime); + {'DOWN', Mon, _, _, {ok,MsaccStats,Prof}} -> + {elapsed_time(StartTime),MsaccStats,Prof}; {'DOWN', Mon, _, _, Other} -> exit(Other) end; -throughput_client(Pid, Mon, Payload, StartTime, N) -> - Pid ! [N|Payload], - throughput_client(Pid, Mon, Payload, StartTime, N - 1). +throughput_client_loop(Pid, Mon, Payload, N, StartTime) -> + Pid ! {self(), N, Payload}, + throughput_client_loop(Pid, Mon, Payload, N - 1, StartTime). + + +-define(prof, none). % none | cprof | eprof + +-if(?prof =:= cprof). +prof_start() -> + cprof:stop(), + cprof:start(), + ok. +-elif(?prof =:= eprof). +prof_start() -> + {ok,_} = eprof:start(), + profiling = eprof:start_profiling(processes()), + ok. +-elif(?prof =:= none). +prof_start() -> + ok. +-endif. + +-if(?prof =:= cprof). +prof_stop() -> + cprof:pause(), + ok. +-elif(?prof =:= eprof). +prof_stop() -> + _ = eprof:stop_profiling(), + ok. +-elif(?prof =:= none). +prof_stop() -> + ok. +-endif. + +-if(?prof =:= cprof). +prof_end() -> + Prof = cprof:analyse(), + cprof:stop(), + Prof. +-elif(?prof =:= eprof). +prof_end() -> + eprof:dump_data(). +-elif(?prof =:= none). +prof_end() -> + []. +-endif. + +-if(?prof =:= cprof). +prof_print(Prof) -> + io:format("~p.~n", [Prof]). +-elif(?prof =:= eprof). +prof_print(Dump) -> + eprof:analyze(undefined, total, [], Dump). +-elif(?prof =:= none). +prof_print([]) -> + ok. +-endif. %%%------------------------------------------------------------------- %%% Test cases helpers diff --git a/lib/ssl/test/ssl_dist_test_lib.erl b/lib/ssl/test/ssl_dist_test_lib.erl index 1b9c853fc4..b8b805b2c4 100644 --- a/lib/ssl/test/ssl_dist_test_lib.erl +++ b/lib/ssl/test/ssl_dist_test_lib.erl @@ -144,6 +144,8 @@ mk_node_cmdline(ListenPort, Name, Args) -> ++ integer_to_list(ListenPort) ++ " " ++ Args ++ " " ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " " + ++ "-kernel inet_dist_connect_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" " + ++ "-kernel inet_dist_listen_options \"[{recbuf,12582912},{sndbuf,12582912},{high_watermark,8388608},{low_watermark,4194304}]\" " ++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" " ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()). @@ -179,8 +181,9 @@ check_ssl_node_up(Socket, Name, Bin) -> Parent = self(), Go = make_ref(), %% Spawn connection handler on test server side - Pid = spawn_link( + Pid = spawn( fun () -> + link(group_leader()), receive Go -> ok end, process_flag(trap_exit, true), tstsrvr_con_loop(Name, Socket, Parent) diff --git a/lib/ssl/test/ssl_engine_SUITE.erl b/lib/ssl/test/ssl_engine_SUITE.erl index e6c82d3eb5..a39a62e550 100644 --- a/lib/ssl/test/ssl_engine_SUITE.erl +++ b/lib/ssl/test/ssl_engine_SUITE.erl @@ -46,10 +46,17 @@ init_per_suite(Config) -> ssl_test_lib:clean_start(), case crypto:get_test_engine() of {ok, EngineName} -> - try crypto:engine_load(<<"dynamic">>, - [{<<"SO_PATH">>, EngineName}, - <<"LOAD">>], - []) of + try + %% The test engine has it's own fake rsa sign/verify that + %% you don't want to use, so exclude it from methods to load: + Methods = + crypto:engine_get_all_methods() -- [engine_method_rsa], + crypto:engine_load(<<"dynamic">>, + [{<<"SO_PATH">>, EngineName}, + <<"LOAD">>], + [], + Methods) + of {ok, Engine} -> [{engine, Engine} |Config]; {error, Reason} -> diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index e39313e5cd..1b432970b6 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -26,6 +26,7 @@ -include_lib("common_test/include/ct.hrl"). -include("ssl_alert.hrl"). +-include("ssl_handshake.hrl"). -include("ssl_internal.hrl"). -include("tls_handshake.hrl"). -include_lib("public_key/include/public_key.hrl"). @@ -42,7 +43,8 @@ all() -> [decode_hello_handshake, decode_empty_server_sni_correctly, select_proper_tls_1_2_rsa_default_hashsign, ignore_hassign_extension_pre_tls_1_2, - unorded_chain, signature_algorithms]. + unorded_chain, signature_algorithms, + encode_decode_srp]. %%-------------------------------------------------------------------- init_per_suite(Config) -> @@ -124,13 +126,13 @@ decode_supported_elliptic_curves_hello_extension_correctly(_Config) -> Len = ListLen + 2, Extension = <<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary>>, % after decoding we should see only valid curves - Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, client), + Extensions = ssl_handshake:decode_hello_extensions(Extension, {3,2}, {3,2}, client), #{elliptic_curves := #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]}} = Extensions. decode_unknown_hello_extension_correctly(_Config) -> FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>, Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, - Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, client), + Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, {3,2}, {3,2}, client), #{renegotiation_info := #renegotiation_info{renegotiated_connection = <<0>>}} = Extensions. @@ -145,12 +147,12 @@ encode_single_hello_sni_extension_correctly(_Config) -> decode_single_hello_sni_extension_correctly(_Config) -> SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08, $t, $e, $s, $t, $., $c, $o, $m>>, - Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, client), + Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, client), #{sni := #sni{hostname = "test.com"}} = Decoded. decode_empty_server_sni_correctly(_Config) -> SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>, - Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, server), + Decoded = ssl_handshake:decode_hello_extensions(SNI, {3,3}, {3,3}, server), #{sni := #sni{hostname = ""}} = Decoded. @@ -190,6 +192,30 @@ unorded_chain(Config) when is_list(Config) -> {ok, _, OrderedChain} = ssl_certificate:certificate_chain(PeerCert, ets:new(foo, []), ExtractedCerts, UnordedChain). +encode_decode_srp(_Config) -> + Exts = #{srp => #srp{username = <<"foo">>}, + sni => #sni{hostname = "bar"}, + renegotiation_info => undefined, + signature_algs => undefined, + alpn => undefined, + next_protocol_negotiation => undefined, + ec_point_formats => undefined, + elliptic_curves => undefined + }, + EncodedExts0 = <<0,20, % Length + 0,12, % SRP extension + 0,4, % Length + 3, % srp_I length + 102,111,111, % username = "foo" + 0,0, % SNI extension + 0,8, % Length + 0,6, % ServerNameLength + 0, % NameType (host_name) + 0,3, % HostNameLength + 98,97,114>>, % hostname = "bar" + EncodedExts0 = <<?UINT16(_),EncodedExts/binary>> = + ssl_handshake:encode_hello_extensions(Exts), + Exts = ssl_handshake:decode_hello_extensions(EncodedExts, {3,3}, {3,3}, client). signature_algorithms(Config) -> Opts = proplists:get_value(server_opts, Config), diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl index 1c7d6b5f9f..878e983bb9 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -64,13 +64,12 @@ next_protocol_not_supported() -> npn_not_supported_server ]. -init_per_suite(Config) -> +init_per_suite(Config0) -> catch crypto:stop(), try crypto:start() of ok -> ssl_test_lib:clean_start(), - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), - proplists:get_value(priv_dir, Config)), + Config = ssl_test_lib:make_rsa_cert(Config0), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -196,10 +195,10 @@ client_negotiate_server_does_not_support(Config) when is_list(Config) -> renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> Data = "hello world", - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, ExpectedProtocol = {ok, <<"http/1.0">>}, @@ -221,7 +220,7 @@ renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- npn_not_supported_client(Config) when is_list(Config) -> - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), PrefProtocols = {client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}, ClientOpts = [PrefProtocols] ++ ClientOpts0, @@ -236,7 +235,7 @@ npn_not_supported_client(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- npn_not_supported_server(Config) when is_list(Config)-> - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, ServerOpts = [AdvProtocols] ++ ServerOpts0, @@ -244,63 +243,24 @@ npn_not_supported_server(Config) when is_list(Config)-> %-------------------------------------------------------------------------------- npn_handshake_session_reused(Config) when is_list(Config)-> - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts =[{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {options, ServerOpts}]), - - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, no_result_msg, []}}, - {options, ClientOpts}]), - - SessionInfo = - receive - {Server, Info} -> - Info - end, - - Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, - - %% Make sure session is registered - ct:sleep(?SLEEP), - - Client1 = - ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, - {mfa, {ssl_test_lib, session_info_result, []}}, - {from, self()}, {options, ClientOpts}]), - - receive - {Client1, SessionInfo} -> - ok; - {Client1, Other} -> - ct:fail(Other) - end, + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). - ssl_test_lib:close(Server), - ssl_test_lib:close(Client), - ssl_test_lib:close(Client1). - %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> Data = "hello world", - ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = ClientExtraOpts ++ ClientOpts0, - ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), ServerOpts = ServerExtraOpts ++ ServerOpts0, {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index ebf8ddbfac..6d26b2df33 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -725,7 +725,7 @@ packet_switch(Config) when is_list(Config) -> {options, [{nodelay, true}, {packet, 2} | ClientOpts]}]), - ssl_test_lib:check_result(Client, ok), + ssl_test_lib:check_result(Client, ok, Server, ok), ssl_test_lib:close(Server), ssl_test_lib:close(Client). @@ -2122,26 +2122,13 @@ active_once_packet(Socket, Data, N) -> active_once_packet(Socket, Data, N-1). active_raw(Socket, Data, N) -> - active_raw(Socket, Data, N, []). - -active_raw(_Socket, _, 0, _) -> + active_raw(Socket, (length(Data) * N)). +active_raw(_Socket, 0) -> ok; -active_raw(Socket, Data, N, Acc) -> +active_raw(Socket, N) -> receive - {ssl, Socket, Byte} when length(Byte) == 1 -> - receive - {ssl, Socket, _} -> - active_raw(Socket, Data, N -1) - end; - {ssl, Socket, Data} -> - active_raw(Socket, Data, N-1, []); - {ssl, Socket, Other} -> - case Acc ++ Other of - Data -> - active_raw(Socket, Data, N-1, []); - NewAcc -> - active_raw(Socket, Data, NewAcc) - end + {ssl, Socket, Bytes} -> + active_raw(Socket, N-length(Bytes)) end. active_packet(Socket, _, 0) -> diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index 5939800001..27b9c258a0 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -64,14 +64,18 @@ payload_tests() -> server_echos_active_huge, client_echos_passive_huge, client_echos_active_once_huge, - client_echos_active_huge]. + client_echos_active_huge, + client_active_once_server_close]. init_per_suite(Config) -> catch crypto:stop(), try crypto:start() of ok -> ssl_test_lib:clean_start(), - {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)), + {ok, _} = + make_certs:all( + proplists:get_value(data_dir, Config), + proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -103,12 +107,13 @@ end_per_group(GroupName, Config) -> Config end. -init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; - TestCase == server_echos_active_once_huge; - TestCase == server_echos_active_huge; - TestCase == client_echos_passive_huge; - TestCase == client_echos_active_once_huge; - TestCase == client_echos_active_huge -> +init_per_testcase(TestCase, Config) + when TestCase == server_echos_passive_huge; + TestCase == server_echos_active_once_huge; + TestCase == server_echos_active_huge; + TestCase == client_echos_passive_huge; + TestCase == client_echos_active_once_huge; + TestCase == client_echos_active_huge -> case erlang:system_info(system_architecture) of "sparc-sun-solaris2.10" -> {skip,"Will take to long time on an old Sparc"}; @@ -117,12 +122,13 @@ init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; Config end; -init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big; - TestCase == server_echos_active_once_big; - TestCase == server_echos_active_big; - TestCase == client_echos_passive_big; - TestCase == client_echos_active_once_big; - TestCase == client_echos_active_big -> +init_per_testcase(TestCase, Config) + when TestCase == server_echos_passive_big; + TestCase == server_echos_active_once_big; + TestCase == server_echos_active_big; + TestCase == client_echos_passive_big; + TestCase == client_echos_active_once_big; + TestCase == client_echos_active_big -> ct:timetrap({seconds, 60}), Config; @@ -144,11 +150,10 @@ server_echos_passive_small(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_passive(Str, 1000, ClientOpts, ServerOpts, - ClientNode, ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 100), + server_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -160,11 +165,10 @@ server_echos_active_once_small(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_active_once(Str, 1000, ClientOpts, ServerOpts, - ClientNode, ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 100), + server_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -176,11 +180,10 @@ server_echos_active_small(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_active(Str, 1000, ClientOpts, ServerOpts, - ClientNode, ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 100), + server_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_passive_small() -> @@ -191,11 +194,10 @@ client_echos_passive_small(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - client_echos_passive(Str, 1000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 100), + client_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_once_small() -> @@ -206,11 +208,10 @@ client_echos_active_once_small(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - client_echos_active_once(Str, 1000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 100), + client_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_small() -> @@ -221,11 +222,10 @@ client_echos_active_small(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - client_echos_active(Str, 1000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 100), + client_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -237,11 +237,10 @@ server_echos_passive_big(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_passive(Str, 50000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 5000), + server_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -253,11 +252,10 @@ server_echos_active_once_big(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_active_once(Str, 50000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 5000), + server_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- @@ -269,11 +267,10 @@ server_echos_active_big(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_active(Str, 50000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 5000), + server_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_passive_big() -> @@ -284,11 +281,10 @@ client_echos_passive_big(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - client_echos_passive(Str, 50000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 5000), + client_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_once_big() -> @@ -299,11 +295,10 @@ client_echos_active_once_big(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - client_echos_active_once(Str, 50000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 5000), + client_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_big() -> @@ -314,11 +309,10 @@ client_echos_active_big(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - client_echos_active(Str, 50000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 5000), + client_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- server_echos_passive_huge() -> @@ -329,11 +323,10 @@ server_echos_passive_huge(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_passive(Str, 500000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 50000), + server_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- server_echos_active_once_huge() -> @@ -344,11 +337,10 @@ server_echos_active_once_huge(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_active_once(Str, 500000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 50000), + server_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- server_echos_active_huge() -> @@ -359,11 +351,10 @@ server_echos_active_huge(Config) when is_list(Config) -> 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), - - Str = "1234567890", - - server_echos_active(Str, 500000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 50000), + server_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_passive_huge() -> @@ -374,10 +365,10 @@ client_echos_passive_huge(Config) when is_list(Config) -> 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), - - Str = "1234567890", - client_echos_passive(Str, 500000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 50000), + client_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_once_huge() -> @@ -388,10 +379,10 @@ client_echos_active_once_huge(Config) when is_list(Config) -> 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), - - Str = "1234567890", - client_echos_active_once(Str, 500000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). + %% + Data = binary:copy(<<"1234567890">>, 50000), + client_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). %%-------------------------------------------------------------------- client_echos_active_huge() -> @@ -402,293 +393,264 @@ client_echos_active_huge(Config) when is_list(Config) -> 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), + %% + Data = binary:copy(<<"1234567890">>, 50000), + client_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + +%%-------------------------------------------------------------------- +client_active_once_server_close() -> + [{doc, "Server sends 500000 bytes and immediately after closes the connection" + "Make sure client recives all data if possible"}]. - Str = "1234567890", - client_echos_active(Str, 500000, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname). +client_active_once_server_close(Config) when is_list(Config) -> + 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), + %% + Data = binary:copy(<<"1234567890">>, 50000), + client_active_once_server_close( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname). + + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- -server_echos_passive(Data, Length, ClientOpts, ServerOpts, - ClientNode, ServerNode, Hostname) -> - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, - {?MODULE, echoer, - [Data, Length]}}, - {options, - [{active, false},{mode, binary} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, sender, - [Data, - Length]}}, - {options, - [{active, false}, {mode, binary} | - ClientOpts]}]), +server_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, echoer, [Length]}}, + {options, [{active, false}, {mode, binary} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sender, [Data]}}, + {options, [{active, false}, {mode, binary} | ClientOpts]}]), + %% ssl_test_lib:check_result(Server, ok, Client, ok), - + %% ssl_test_lib:close(Server), ssl_test_lib:close(Client). -server_echos_active_once(Data, Length, ClientOpts, ServerOpts, ClientNode, - ServerNode, Hostname) -> - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, - {?MODULE, echoer_once, - [Data, Length]}}, - {options, [{active, once}, - {mode, binary}| - ServerOpts]}]), +server_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, echoer_active_once, [Length]}}, + {options, [{active, once}, {mode, binary} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, sender_once, - [Data, Length]}}, - {options, [{active, once}, - {mode, binary} | - ClientOpts]}]), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sender_active_once, [Data]}}, + {options, [{active, once}, {mode, binary} | ClientOpts]}]), + %% ssl_test_lib:check_result(Server, ok, Client, ok), - + %% ssl_test_lib:close(Server), ssl_test_lib:close(Client). -server_echos_active(Data, Length, ClientOpts, ServerOpts, - ClientNode, ServerNode, Hostname) -> - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, - {?MODULE, echoer_active, - [Data, Length]}}, - {options, - [{active, true}, - {mode, binary} | ServerOpts]}]), +server_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, echoer_active, [Length]}}, + {options, [{active, true}, {mode, binary} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, sender_active, - [Data, - Length]}}, - {options, - [{active, true}, {mode, binary} - | ClientOpts]}]), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, sender_active, [Data]}}, + {options, [{active, true}, {mode, binary} | ClientOpts]}]), + %% ssl_test_lib:check_result(Server, ok, Client, ok), - + %% ssl_test_lib:close(Server), ssl_test_lib:close(Client). -client_echos_passive(Data, Length, ClientOpts, ServerOpts, - ClientNode, ServerNode, Hostname) -> - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, - {?MODULE, sender, - [Data, Length]}}, - {options, - [{active, false}, {mode, binary} | - ServerOpts]}]), +client_echos_passive( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sender, [Data]}}, + {options, [{active, false}, {mode, binary} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, echoer, - [Data, - Length]}}, - {options, - [{active, false}, {mode, binary} - | ClientOpts]}]), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, echoer, [Length]}}, + {options, [{active, false}, {mode, binary} | ClientOpts]}]), + %% ssl_test_lib:check_result(Server, ok, Client, ok), - + %% ssl_test_lib:close(Server), ssl_test_lib:close(Client). -client_echos_active_once(Data, Length, - ClientOpts, ServerOpts, ClientNode, ServerNode, - Hostname) -> - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, - {?MODULE, sender_once, - [Data, Length]}}, - {options, [{active, once}, - {mode, binary} | - ServerOpts]}]), +client_echos_active_once( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sender_active_once, [Data]}}, + {options, [{active, once}, {mode, binary} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, echoer_once, - [Data, - Length]}}, - {options,[{active, once}, - {mode, binary} - | ClientOpts]}]), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, echoer_active_once, [Length]}}, + {options,[{active, once}, {mode, binary} | ClientOpts]}]), + %% ssl_test_lib:check_result(Server, ok, Client, ok), - + %% ssl_test_lib:close(Server), ssl_test_lib:close(Client). -client_echos_active(Data, Length, ClientOpts, ServerOpts, ClientNode, - ServerNode, - Hostname) -> - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, - {?MODULE, sender_active, - [Data, Length]}}, - {options, [{active, true}, - {mode, binary} - | ServerOpts]}]), +client_echos_active( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, sender_active, [Data]}}, + {options, [{active, true}, {mode, binary} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, - {?MODULE, echoer_active, - [Data, - Length]}}, - {options, [{active, true}, - {mode, binary} - | ClientOpts]}]), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, echoer_active, [Length]}}, + {options, [{active, true}, {mode, binary} | ClientOpts]}]), + % ssl_test_lib:check_result(Server, ok, Client, ok), - + %% ssl_test_lib:close(Server), ssl_test_lib:close(Client). -send(_, _, _, 0,_) -> +client_active_once_server_close( + Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) -> + Length = byte_size(Data), + Server = + ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, send_close, [Data]}}, + {options, [{active, once}, {mode, binary} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, active_once_recv, [Length]}}, + {options,[{active, once}, {mode, binary} | ClientOpts]}]), + %% + ssl_test_lib:check_result(Server, ok, Client, ok). + +send(_Socket, _Data, 0, _) -> ok; -send(Socket, Data, Size, Repeate,F) -> - NewData = lists:duplicate(Size div 10, Data), - ssl:send(Socket, NewData), - F(), - send(Socket, Data, Size, Repeate - 1,F). - -sender(Socket, Data, Size) -> - ok = send(Socket, Data, Size, 100, fun() -> do_recv(Socket, Data, Size, <<>>, false) end), +send(Socket, Data, Count, RecvEcho) -> + ok = ssl:send(Socket, Data), + RecvEcho(), + send(Socket, Data, Count - 1, RecvEcho). + +send_close(Socket, Data) -> + ok = ssl:send(Socket, Data), + ssl:close(Socket). + +sender(Socket, Data) -> ct:log("Sender recv: ~p~n", [ssl:getopts(Socket, [active])]), - ok. - -sender_once(Socket, Data, Size) -> - send(Socket, Data, Size, 100, - fun() -> do_active_once(Socket, Data, Size, <<>>, false) end), - ct:log("Sender active once: ~p~n", - [ssl:getopts(Socket, [active])]), - ok. - -sender_active(Socket, Data, Size) -> - F = fun() -> do_active(Socket, Data, Size, <<>>, false) end, - send(Socket, Data, Size, 100, F), + send(Socket, Data, 100, + fun() -> + ssl_test_lib:recv_disregard(Socket, byte_size(Data)) + end). + +sender_active_once(Socket, Data) -> + ct:log("Sender active once: ~p~n", [ssl:getopts(Socket, [active])]), + send(Socket, Data, 100, + fun() -> + ssl_test_lib:active_once_disregard(Socket, byte_size(Data)) + end). + +sender_active(Socket, Data) -> ct:log("Sender active: ~p~n", [ssl:getopts(Socket, [active])]), - ok. + send(Socket, Data, 100, + fun() -> + ssl_test_lib:active_disregard(Socket, byte_size(Data)) + end). -echoer(Socket, Data, Size) -> +echoer(Socket, Size) -> ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]), - echo(fun() -> do_recv(Socket, Data, Size, <<>>, true) end, 100). + echo_recv(Socket, Size * 100). -echoer_once(Socket, Data, Size) -> - ct:log("Echoer active once: ~p ~n", - [ssl:getopts(Socket, [active])]), - echo(fun() -> do_active_once(Socket, Data, Size, <<>>, true) end, 100). +echoer_active_once(Socket, Size) -> + ct:log("Echoer active once: ~p~n", [ssl:getopts(Socket, [active])]), + echo_active_once(Socket, Size * 100). -echoer_active(Socket, Data, Size) -> +echoer_active(Socket, Size) -> ct:log("Echoer active: ~p~n", [ssl:getopts(Socket, [active])]), - echo(fun() -> do_active(Socket, Data, Size, <<>>, true) end, 100). - -echo(_Fun, 0) -> ok; -echo(Fun, N) -> - Fun(), - echo(Fun, N-1). + echo_active(Socket, Size * 100). -do_recv(_Socket, _Data, 0, _Acc, true) -> +%% Receive Size bytes +echo_recv(_Socket, 0) -> ok; -do_recv(_Socket, Data, 0, Acc, false) -> - Data = lists:sublist(binary_to_list(Acc), 10); +echo_recv(Socket, Size) -> + {ok, Data} = ssl:recv(Socket, 0), + ok = ssl:send(Socket, Data), + echo_recv(Socket, Size - byte_size(Data)). -do_recv(Socket, Data, Size, Acc, Echo) -> - {ok, NewData} = ssl:recv(Socket, 0), - NewSize = size(NewData), - case Echo of - true -> - ssl:send(Socket, NewData), - NewSize = size(NewData), - do_recv(Socket, Data, Size - NewSize, [], Echo); - false -> - case size(Acc) < 10 of - true -> - do_recv(Socket, Data, Size - NewSize, - <<Acc/binary, NewData/binary>>, Echo); - false -> - do_recv(Socket, Data, Size - NewSize, Acc, Echo) - end - end. - -do_active_once(_Socket, _Data, 0, _Acc, true) -> +%% Receive Size bytes +echo_active_once(_Socket, 0) -> ok; -do_active_once(_Socket, Data, 0, Acc, false) -> - Data = lists:sublist(binary_to_list(Acc), 10); - -do_active_once(Socket, Data, Size, Acc, Echo) -> - receive - {ssl, Socket, NewData} -> - NewSize = size(NewData), - case Echo of - true -> - ssl:send(Socket, NewData), - ssl:setopts(Socket, [{active, once}]), - do_active_once(Socket, Data, Size - NewSize, [], Echo); - false -> - case size(Acc) < 10 of - true -> - ssl:setopts(Socket, [{active, once}]), - do_active_once(Socket, Data, Size - NewSize, - <<Acc/binary, NewData/binary>>, - Echo); - false -> - ssl:setopts(Socket, [{active, once}]), - do_active_once(Socket, Data, - Size - NewSize, Acc, Echo) - end - end +echo_active_once(Socket, Size) -> + receive + {ssl, Socket, Data} -> + ok = ssl:send(Socket, Data), + NewSize = Size - byte_size(Data), + ssl:setopts(Socket, [{active, once}]), + echo_active_once(Socket, NewSize) end. - -do_active(_Socket, _Data, 0, _Acc, true) -> + +%% Receive Size bytes +echo_active(_Socket, 0) -> ok; -do_active(_Socket, Data, 0, Acc, false) -> - Data = lists:sublist(binary_to_list(Acc), 10); - -do_active(Socket, Data, Size, Acc, Echo) -> - receive - {ssl, Socket, NewData} -> - NewSize = size(NewData), - case Echo of - true -> - ssl:send(Socket, NewData), - do_active(Socket, Data, Size - NewSize, [], Echo); - false -> - case size(Acc) < 10 of - true -> - do_active(Socket, Data, Size - NewSize, - <<Acc/binary, NewData/binary>>, - Echo); - false -> - do_active(Socket, Data, - Size - NewSize, Acc, Echo) - end - end - end. +echo_active(Socket, Size) -> + receive + {ssl, Socket, Data} -> + ok = ssl:send(Socket, Data), + echo_active(Socket, Size - byte_size(Data)) + end. + diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl index 25d2cb300d..6f11e2bbe8 100644 --- a/lib/ssl/test/ssl_pem_cache_SUITE.erl +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -44,11 +44,8 @@ init_per_suite(Config0) -> try crypto:start() of ok -> 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)), - Config1 = ssl_test_lib:make_dsa_cert(Config0), - ssl_test_lib:cert_options(Config1) + %% make rsa certs + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. @@ -86,8 +83,8 @@ pem_cleanup() -> [{doc, "Test pem cache invalidate mechanism"}]. pem_cleanup(Config)when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -118,8 +115,8 @@ invalid_insert() -> invalid_insert(Config)when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), BadClientOpts = [{cacertfile, "tmp/does_not_exist.pem"} | proplists:delete(cacertfile, ClientOpts)], Server = diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index a0fab58b9d..7f33fe3204 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -48,7 +48,8 @@ all() -> session_cache_process_list, session_cache_process_mnesia, client_unique_session, - max_table_size + max_table_size, + save_specific_session ]. groups() -> @@ -60,10 +61,7 @@ init_per_suite(Config0) -> ok -> 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)), - Config = ssl_test_lib:make_dsa_cert(Config0), - ssl_test_lib:cert_options(Config) + ssl_test_lib:make_rsa_cert(Config0) catch _:_ -> {skip, "Crypto did not start"} end. @@ -97,7 +95,10 @@ init_per_testcase(session_cleanup, Config) -> init_per_testcase(client_unique_session, Config) -> ct:timetrap({seconds, 40}), Config; - +init_per_testcase(save_specific_session, Config) -> + ssl_test_lib:clean_start(), + ct:timetrap({seconds, 5}), + Config; init_per_testcase(max_table_size, Config) -> ssl:stop(), application:load(ssl), @@ -141,7 +142,7 @@ end_per_testcase(max_table_size, Config) -> end_per_testcase(default_action, Config); end_per_testcase(Case, Config) when Case == session_cache_process_list; Case == session_cache_process_mnesia -> - ets:delete(ssl_test), + catch ets:delete(ssl_test), Config; end_per_testcase(_, Config) -> Config. @@ -154,8 +155,8 @@ client_unique_session() -> "sets up many connections"}]. client_unique_session(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_opts, Config), - ServerOpts = proplists:get_value(server_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -164,8 +165,7 @@ client_unique_session(Config) when is_list(Config) -> {tcp_options, [{active, false}]}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - LastClient = clients_start(Server, - ClientNode, Hostname, Port, ClientOpts, client_unique_session, 20), + LastClient = clients_start(Server, ClientNode, Hostname, Port, ClientOpts, 20), receive {LastClient, {ok, _}} -> ok @@ -185,8 +185,8 @@ session_cleanup() -> "does not grow and grow ..."}]. session_cleanup(Config) when is_list(Config) -> process_flag(trap_exit, true), - 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_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -254,13 +254,75 @@ session_cache_process_mnesia(Config) when is_list(Config) -> session_cache_process(mnesia,Config). %%-------------------------------------------------------------------- +save_specific_session() -> + [{doc, "Test that we can save a specific client session" + }]. +save_specific_session(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + Server ! listen, + + Client2 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + SessionID1 = + receive + {Client1, S1} -> + S1 + end, + + SessionID2 = + receive + {Client2, S2} -> + S2 + end, + + true = SessionID1 =/= SessionID2, + + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + 2 = ssl_session_cache:size(ClientCache), + + Server ! listen, + + Client3 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_session, SessionID2} | ClientOpts]}]), + receive + {Client3, SessionID2} -> + ok; + {Client3, SessionID3}-> + ct:fail({got, SessionID3, expected, SessionID2}); + Other -> + ct:fail({got,Other}) + end. + +%%-------------------------------------------------------------------- max_table_size() -> [{doc,"Test max limit on session table"}]. max_table_size(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = proplists:get_value(client_verification_opts, Config), - ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_rsa_verify_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -270,7 +332,7 @@ max_table_size(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), LastClient = clients_start(Server, - ClientNode, Hostname, Port, ClientOpts, max_table_size, 20), + ClientNode, Hostname, Port, ClientOpts, 20), receive {LastClient, {ok, _}} -> ok @@ -426,25 +488,27 @@ session_loop(Sess) -> %%-------------------------------------------------------------------- session_cache_process(_Type,Config) when is_list(Config) -> - ssl_basic_SUITE:reuse_session(Config). + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ssl_test_lib:reuse_session(ClientOpts, ServerOpts, Config). -clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, Test, 0) -> +clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, 0) -> %% Make sure session is registered ct:sleep(?SLEEP * 2), ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {?MODULE, connection_info_result, []}}, - {from, self()}, {options, test_copts(Test, 0, ClientOpts)}]); -clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N) -> + {from, self()}, {options, ClientOpts}]); +clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N) -> spawn_link(ssl_test_lib, start_client, [[{node, ClientNode}, {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, test_copts(Test, N, ClientOpts)}]]), + {from, self()}, {options, ClientOpts}]]), Server ! listen, wait_for_server(), - clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N-1). + clients_start(Server, ClientNode, Hostname, Port, ClientOpts, N-1). connection_info_result(Socket) -> ssl:connection_information(Socket, [protocol, cipher_suite]). @@ -481,21 +545,3 @@ get_delay_timers() -> wait_for_server() -> ct:sleep(100). - - -test_copts(_, 0, ClientOpts) -> - ClientOpts; -test_copts(max_table_size, N, ClientOpts) -> - Version = tls_record:highest_protocol_version([]), - CipherSuites = %%lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), -[ Y|| Y = {Alg,_, _, _} <- lists:map(fun(X) -> ssl_cipher_format:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), Alg =/= ecdhe_ecdsa, Alg =/= ecdh_ecdsa, Alg =/= ecdh_rsa, Alg =/= ecdhe_rsa, Alg =/= dhe_dss, Alg =/= dss], - case length(CipherSuites) of - M when M >= N -> - Cipher = lists:nth(N, CipherSuites), - ct:pal("~p",[Cipher]), - [{ciphers, [Cipher]} | ClientOpts]; - _ -> - ClientOpts - end; -test_copts(_, _, ClientOpts) -> - ClientOpts. diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 8a2f0824fb..0173b98e1a 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -26,6 +26,7 @@ %% Note: This directive should only be used in test suites. -compile(export_all). +-compile(nowarn_export_all). -record(sslsocket, { fd = nil, pid = nil}). -define(SLEEP, 1000). @@ -522,7 +523,7 @@ cert_options(Config) -> {client_verification_opts, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, - {ssl_imp, new}]}, + {verify, verify_peer}]}, {client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFileDigitalSignatureOnly}, {keyfile, ClientKeyFile}, @@ -1460,19 +1461,10 @@ cipher_result(Socket, Result) -> %% Importante to send two packets here %% to properly test "cipher state" handling ssl:send(Socket, "Hello\n"), - receive - {ssl, Socket, "H"} -> - ssl:send(Socket, " world\n"), - receive_rizzo_duong_beast(); - {ssl, Socket, "Hello\n"} -> - ssl:send(Socket, " world\n"), - receive - {ssl, Socket, " world\n"} -> - ok - end; - Other -> - {unexpected, Other} - end. + "Hello\n" = active_recv(Socket, length( "Hello\n")), + ssl:send(Socket, " world\n"), + " world\n" = active_recv(Socket, length(" world\n")), + ok. session_info_result(Socket) -> {ok, Info} = ssl:connection_information(Socket, [session_id, cipher_suite]), @@ -1545,7 +1537,12 @@ init_tls_version(Version, Config) -> clean_tls_version(Config) -> proplists:delete(protocol_opts, proplists:delete(protocol, Config)). - + +sufficient_crypto_support(Version) + when Version == 'tlsv1.3' -> + CryptoSupport = crypto:supports(), + lists:member(rsa_pkcs1_pss_padding, proplists:get_value(rsa_opts, CryptoSupport)) andalso + lists:member(x448, proplists:get_value(curves, CryptoSupport)); sufficient_crypto_support(Version) when Version == 'tlsv1.2'; Version == 'dtlsv1.2' -> CryptoSupport = crypto:supports(), @@ -1591,36 +1588,72 @@ v_1_2_check(ecdh_rsa, ecdh_ecdsa) -> v_1_2_check(_, _) -> false. -send_recv_result_active(Socket) -> - ssl:send(Socket, "Hello world"), - receive - {ssl, Socket, "H"} -> - receive - {ssl, Socket, "ello world"} -> - ok - end; - {ssl, Socket, "Hello world"} -> - ok - end. - send_recv_result(Socket) -> - ssl:send(Socket, "Hello world"), - {ok,"Hello world"} = ssl:recv(Socket, 11), + Data = "Hello world", + ssl:send(Socket, Data), + {ok, Data} = ssl:recv(Socket, length(Data)), + ok. + +send_recv_result_active(Socket) -> + Data = "Hello world", + ssl:send(Socket, Data), + Data = active_recv(Socket, length(Data)), ok. send_recv_result_active_once(Socket) -> - ssl:send(Socket, "Hello world"), - receive - {ssl, Socket, "H"} -> - ssl:setopts(Socket, [{active, once}]), - receive - {ssl, Socket, "ello world"} -> - ok - end; - {ssl, Socket, "Hello world"} -> - ok + Data = "Hello world", + ssl:send(Socket, Data), + active_once_recv_list(Socket, length(Data)). + +active_recv(Socket, N) -> + active_recv(Socket, N, []). + +active_recv(_Socket, 0, Acc) -> + Acc; +active_recv(Socket, N, Acc) -> + receive + {ssl, Socket, Bytes} -> + active_recv(Socket, N-length(Bytes), Acc ++ Bytes) + end. + +active_once_recv(_Socket, 0) -> + ok; +active_once_recv(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + ssl:setopts(Socket, [{active, once}]), + active_once_recv(Socket, N-byte_size(Bytes)) end. +active_once_recv_list(_Socket, 0) -> + ok; +active_once_recv_list(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + ssl:setopts(Socket, [{active, once}]), + active_once_recv_list(Socket, N-length(Bytes)) + end. +recv_disregard(_Socket, 0) -> + ok; +recv_disregard(Socket, N) -> + {ok, Bytes} = ssl:recv(Socket, 0), + recv_disregard(Socket, N-byte_size(Bytes)). + +active_disregard(_Socket, 0) -> + ok; +active_disregard(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + active_disregard(Socket, N-byte_size(Bytes)) + end. +active_once_disregard(_Socket, 0) -> + ok; +active_once_disregard(Socket, N) -> + receive + {ssl, Socket, Bytes} -> + ssl:setopts(Socket, [{active, once}]), + active_once_disregard(Socket, N-byte_size(Bytes)) + end. is_sane_ecc(openssl) -> case os:cmd("openssl version") of "OpenSSL 1.0.0a" ++ _ -> % Known bug in openssl @@ -1706,10 +1739,10 @@ openssl_dsa_support() -> true; "LibreSSL" ++ _ -> false; - "OpenSSL 1.1" ++ Rest -> + "OpenSSL 1.1" ++ _Rest -> false; "OpenSSL 1.0.1" ++ Rest -> - hd(Rest) >= s; + hd(Rest) >= $s; _ -> true end. @@ -1746,8 +1779,6 @@ openssl_sane_client_cert() -> false; "LibreSSL 2.0" ++ _ -> false; - "LibreSSL 2.0" ++ _ -> - false; "OpenSSL 1.0.1s-freebsd" -> false; "OpenSSL 1.0.0" ++ _ -> @@ -2160,3 +2191,98 @@ server_msg(Server, ServerMsg) -> Unexpected -> ct:fail(Unexpected) end. + +session_id(Socket) -> + {ok, [{session_id, ID}]} = ssl:connection_information(Socket, [session_id]), + ID. + +reuse_session(ClientOpts, ServerOpts, Config) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server0 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port0 = ssl_test_lib:inet_port(Server0), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + Server0 ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + SID = receive + {Client0, Id0} -> + Id0 + end, + + receive + {Client1, SID} -> + ok + after ?SLEEP -> + ct:fail(session_not_reused) + end, + + Server0 ! listen, + + Client2 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port0}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, false} + | ClientOpts]}]), + receive + {Client2, SID} -> + ct:fail(session_reused_when_session_reuse_disabled_by_client); + {Client2, _} -> + ok + end, + + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2), + + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, [{reuse_sessions, false} |ServerOpts]}]), + Port1 = ssl_test_lib:inet_port(Server1), + + Client3 = ssl_test_lib:start_client([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, [{reuse_sessions, save} | ClientOpts]}]), + SID1 = receive + {Client3, Id3} -> + Id3 + end, + + Server1 ! listen, + + Client4 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port1}, {host, Hostname}, + {mfa, {ssl_test_lib, session_id, []}}, + {from, self()}, {options, ClientOpts}]), + + receive + {Client4, SID1} -> + ct:fail(session_reused_when_session_reuse_disabled_by_server); + {Client4, _} -> + ok + end, + + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client3), + ssl_test_lib:close(Client4). + diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 5a38f5f9c1..d180021439 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -91,6 +91,7 @@ all_versions_tests() -> erlang_server_openssl_client_anon_with_cert, erlang_server_openssl_client_reuse_session, erlang_client_openssl_server_renegotiate, + erlang_client_openssl_server_renegotiate_after_client_data, erlang_client_openssl_server_nowrap_seqnum, erlang_server_openssl_client_nowrap_seqnum, erlang_client_openssl_server_no_server_ca_cert, @@ -259,8 +260,9 @@ special_init(TestCase, Config) when Config; special_init(TestCase, Config) when TestCase == erlang_client_openssl_server_renegotiate; - TestCase == erlang_client_openssl_server_nowrap_seqnum; - TestCase == erlang_server_openssl_client_nowrap_seqnum + TestCase == erlang_client_openssl_server_nowrap_seqnum; + TestCase == erlang_server_openssl_client_nowrap_seqnum; + TestCase == erlang_client_openssl_server_renegotiate_after_client_data -> {ok, Version} = application:get_env(ssl, protocol_version), check_sane_openssl_renegotaite(Config, Version); @@ -760,8 +762,8 @@ erlang_client_openssl_server_renegotiate() -> [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -770,12 +772,14 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile, "-msg"], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -800,6 +804,53 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. +%%-------------------------------------------------------------------- +erlang_client_openssl_server_renegotiate_after_client_data() -> + [{doc,"Test erlang client when openssl server issuses a renegotiate after reading client data"}]. +erlang_client_openssl_server_renegotiate_after_client_data(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + ErlData = "From erlang to openssl", + OpenSslData = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, + "-cert", CertFile, "-key", KeyFile, "-msg"], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + send_wait_send, [[ErlData, OpenSslData]]}}, + {options, ClientOpts}]), + + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, OpenSslData), + + ssl_test_lib:check_result(Client, ok), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + ssl_test_lib:close(Client), + process_flag(trap_exit, false), + ok. %%-------------------------------------------------------------------- @@ -810,7 +861,7 @@ erlang_client_openssl_server_nowrap_seqnum() -> " to lower treashold substantially."}]. erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -819,12 +870,14 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> N = 10, Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile, "-msg"], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -853,7 +906,7 @@ erlang_server_openssl_client_nowrap_seqnum() -> " to lower treashold substantially."}]. erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1602,8 +1655,8 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = ErlangClientOpts ++ ClientOpts0, {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1611,6 +1664,7 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), @@ -1620,10 +1674,12 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens [] -> ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile,"-key", KeyFile]; [Opt, Value] -> ["s_server", Opt, Value, "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile,"-key", KeyFile] end, @@ -1648,8 +1704,8 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = proplists:get_value(server_rsa_opts, Config), - ClientOpts0 = proplists:get_value(client_rsa_opts, Config), + ServerOpts = proplists:get_value(server_rsa_verify_opts, Config), + ClientOpts0 = proplists:get_value(client_rsa_verify_opts, Config), ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1657,12 +1713,14 @@ start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callba Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), Exe = "openssl", Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), ssl_test_lib:wait_for_openssl_server(Port, proplists:get_value(protocol, Config)), @@ -1780,8 +1838,8 @@ start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Ca start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = ssl_test_lib:ssl_options(server_rsa_opts, Config), - ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1789,6 +1847,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac Data = "From openssl to erlang", Port = ssl_test_lib:inet_port(node()), + CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), Version = ssl_test_lib:protocol_version(Config), @@ -1796,6 +1855,7 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac Exe = "openssl", Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-CAfile", CaCertFile, "-cert", CertFile, "-key", KeyFile], OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), @@ -1924,6 +1984,12 @@ server_sent_garbage(Socket) -> {error, closed} == ssl:send(Socket, "data") end. + +send_wait_send(Socket, [ErlData, OpenSslData]) -> + ssl:send(Socket, ErlData), + ct:sleep(?SLEEP), + ssl:send(Socket, ErlData), + erlang_ssl_receive(Socket, OpenSslData). check_openssl_sni_support(Config) -> HelpText = os:cmd("openssl s_client --help"), diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 75d959accf..3527062a8a 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 9.1 +SSL_VSN = 9.1.2 diff --git a/lib/stdlib/doc/src/array.xml b/lib/stdlib/doc/src/array.xml index db0ab42372..aa1577a067 100644 --- a/lib/stdlib/doc/src/array.xml +++ b/lib/stdlib/doc/src/array.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>array.xml</file> </header> - <module>array</module> + <module since="">array</module> <modulesummary>Functional, extendible arrays.</modulesummary> <description> <p>Functional, extendible arrays. Arrays can have fixed size, or can grow @@ -137,7 +137,7 @@ A3 = array:fix(A2).</pre> <funcs> <func> - <name name="default" arity="1"/> + <name name="default" arity="1" since=""/> <fsummary>Get the value used for uninitialized entries.</fsummary> <desc><marker id="default-1"/> <p>Gets the value used for uninitialized entries.</p> @@ -146,7 +146,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="fix" arity="1"/> + <name name="fix" arity="1" since=""/> <fsummary>Fix the array size.</fsummary> <desc><marker id="fix-1"/> <p>Fixes the array size. This prevents it from growing automatically @@ -157,7 +157,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="foldl" arity="3"/> + <name name="foldl" arity="3" since=""/> <fsummary>Fold the array elements using the specified function and initial accumulator value.</fsummary> <desc><marker id="foldl-3"/> @@ -172,7 +172,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="foldr" arity="3"/> + <name name="foldr" arity="3" since=""/> <fsummary>Fold the array elements right-to-left using the specified function and initial accumulator value.</fsummary> <desc><marker id="foldr-3"/> @@ -186,7 +186,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Equivalent to <c>from_list(List, undefined)</c>.</fsummary> <desc><marker id="from_list-1"/> <p>Equivalent to @@ -195,7 +195,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="from_list" arity="2"/> + <name name="from_list" arity="2" since=""/> <fsummary>Convert a list to an extendible array.</fsummary> <desc><marker id="from_list-2"/> <p>Converts a list to an extendible array. <c><anno>Default</anno></c> @@ -208,7 +208,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="from_orddict" arity="1"/> + <name name="from_orddict" arity="1" since=""/> <fsummary>Equivalent to <c>from_orddict(Orddict, undefined)</c>. </fsummary> <desc><marker id="from_orddict-1"/> @@ -218,7 +218,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="from_orddict" arity="2"/> + <name name="from_orddict" arity="2" since=""/> <fsummary>Convert an ordered list of pairs <c>{Index, Value}</c> to a corresponding extendible array.</fsummary> <desc><marker id="from_orddict-2"/> @@ -234,7 +234,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="get" arity="2"/> + <name name="get" arity="2" since=""/> <fsummary>Get the value of entry <c>I</c>.</fsummary> <desc><marker id="get-2"/> <p>Gets the value of entry <c><anno>I</anno></c>. If @@ -249,7 +249,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="is_array" arity="1"/> + <name name="is_array" arity="1" since=""/> <fsummary>Returns <c>true</c> if <c>X</c> is an array, otherwise <c>false</c>.</fsummary> <desc><marker id="is_array-1"/> @@ -261,7 +261,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="is_fix" arity="1"/> + <name name="is_fix" arity="1" since=""/> <fsummary>Check if the array has fixed size.</fsummary> <desc><marker id="is_fix-1"/> <p>Checks if the array has fixed size. Returns <c>true</c> if the array @@ -271,7 +271,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="map" arity="2"/> + <name name="map" arity="2" since=""/> <fsummary>Map the specified function onto each array element.</fsummary> <desc><marker id="map-2"/> <p>Maps the specified function onto each array element. The elements are @@ -285,7 +285,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Create a new, extendible array with initial size zero. </fsummary> <desc><marker id="new-0"/> @@ -296,7 +296,7 @@ A3 = array:fix(A2).</pre> </func> <func> - <name name="new" arity="1"/> + <name name="new" arity="1" since=""/> <fsummary>Create a new array according to the specified options. </fsummary> <desc><marker id="new-1"/> @@ -346,7 +346,7 @@ array:new([{size,10},{fixed,false},{default,-1}])</pre> </func> <func> - <name name="new" arity="2"/> + <name name="new" arity="2" since=""/> <fsummary>Create a new array according to the specified size and options. </fsummary> <desc><marker id="new-2"/> @@ -370,7 +370,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="relax" arity="1"/> + <name name="relax" arity="1" since=""/> <fsummary>Make the array resizable.</fsummary> <desc><marker id="relax-1"/> <p>Makes the array resizable. (Reverses the effects of @@ -380,7 +380,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="reset" arity="2"/> + <name name="reset" arity="2" since=""/> <fsummary>Reset entry <c>I</c> to the default value for the array. </fsummary> <desc><marker id="reset-2"/> @@ -399,7 +399,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="resize" arity="1"/> + <name name="resize" arity="1" since=""/> <fsummary>Change the array size to that reported by <c>sparse_size/1</c>. </fsummary> <desc><marker id="resize-1"/> @@ -413,7 +413,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="resize" arity="2"/> + <name name="resize" arity="2" since=""/> <fsummary>Change the array size.</fsummary> <desc><marker id="resize-2"/> <p>Change the array size. If <c><anno>Size</anno></c> is not a @@ -424,7 +424,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="set" arity="3"/> + <name name="set" arity="3" since=""/> <fsummary>Set entry <c>I</c> of the array to <c>Value</c>.</fsummary> <desc><marker id="set-3"/> <p>Sets entry <c><anno>I</anno></c> of the array to @@ -441,7 +441,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Get the number of entries in the array.</fsummary> <desc><marker id="size-1"/> <p>Gets the number of entries in the array. Entries are numbered from @@ -454,7 +454,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="sparse_foldl" arity="3"/> + <name name="sparse_foldl" arity="3" since=""/> <fsummary>Fold the array elements using the specified function and initial accumulator value, skipping default-valued entries.</fsummary> <desc><marker id="sparse_foldl-3"/> @@ -469,7 +469,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="sparse_foldr" arity="3"/> + <name name="sparse_foldr" arity="3" since=""/> <fsummary>Fold the array elements right-to-left using the specified function and initial accumulator value, skipping default-valued entries.</fsummary> @@ -485,7 +485,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="sparse_map" arity="2"/> + <name name="sparse_map" arity="2" since=""/> <fsummary>Map the specified function onto each array element, skipping default-valued entries.</fsummary> <desc><marker id="sparse_map-2"/> @@ -498,7 +498,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="sparse_size" arity="1"/> + <name name="sparse_size" arity="1" since=""/> <fsummary>Get the number of entries in the array up until the last non-default-valued entry.</fsummary> <desc><marker id="sparse_size-1"/> @@ -512,7 +512,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="sparse_to_list" arity="1"/> + <name name="sparse_to_list" arity="1" since=""/> <fsummary>Convert the array to a list, skipping default-valued entries. </fsummary> <desc><marker id="sparse_to_list-1"/> @@ -522,7 +522,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="sparse_to_orddict" arity="1"/> + <name name="sparse_to_orddict" arity="1" since=""/> <fsummary>Convert the array to an ordered list of pairs <c>{Index, Value}</c>, skipping default-valued entries.</fsummary> <desc><marker id="sparse_to_orddict-1"/> @@ -534,7 +534,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert the array to a list.</fsummary> <desc><marker id="to_list-1"/> <p>Converts the array to a list.</p> @@ -545,7 +545,7 @@ array:new(100, {default,0})</pre> </func> <func> - <name name="to_orddict" arity="1"/> + <name name="to_orddict" arity="1" since=""/> <fsummary>Convert the array to an ordered list of pairs <c>{Index, Value}</c>.</fsummary> <desc><marker id="to_orddict-1"/> diff --git a/lib/stdlib/doc/src/base64.xml b/lib/stdlib/doc/src/base64.xml index cfa1ecc006..479072ba4f 100644 --- a/lib/stdlib/doc/src/base64.xml +++ b/lib/stdlib/doc/src/base64.xml @@ -29,7 +29,7 @@ <rev></rev> <file>base64.xml</file> </header> - <module>base64</module> + <module since="">base64</module> <modulesummary>Provides base64 encode and decode, see RFC 2045.</modulesummary> <description> @@ -51,10 +51,10 @@ <funcs> <func> - <name name="decode" arity="1"/> - <name name="decode_to_string" arity="1"/> - <name name="mime_decode" arity="1"/> - <name name="mime_decode_to_string" arity="1"/> + <name name="decode" arity="1" since=""/> + <name name="decode_to_string" arity="1" since=""/> + <name name="mime_decode" arity="1" since=""/> + <name name="mime_decode_to_string" arity="1" since=""/> <fsummary>Decode a base64 encoded string to data.</fsummary> <type variable="Base64" name_i="1"/> <type variable="Data" name_i="1"/> @@ -69,8 +69,8 @@ </func> <func> - <name name="encode" arity="1"/> - <name name="encode_to_string" arity="1"/> + <name name="encode" arity="1" since=""/> + <name name="encode_to_string" arity="1" since=""/> <fsummary>Encode data into base64.</fsummary> <type variable="Data"/> <type variable="Base64" name_i="1"/> diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml index 213170df7f..8bb4cf9101 100644 --- a/lib/stdlib/doc/src/beam_lib.xml +++ b/lib/stdlib/doc/src/beam_lib.xml @@ -28,7 +28,7 @@ <date>1999-10-30</date> <rev>PA1</rev> </header> - <module>beam_lib</module> + <module since="">beam_lib</module> <modulesummary>An interface to the BEAM file format.</modulesummary> <description> <p>This module provides an interface to files created by @@ -267,7 +267,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> <funcs> <func> - <name name="all_chunks" arity="1"/> + <name name="all_chunks" arity="1" since="OTP 18.2"/> <fsummary>Read all chunks from a BEAM file or binary</fsummary> <desc> <p>Reads chunk data for all chunks.</p> @@ -275,7 +275,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="build_module" arity="1"/> + <name name="build_module" arity="1" since="OTP 18.2"/> <fsummary>Create a BEAM module from a list of chunks.</fsummary> <desc> <p>Builds a BEAM module (as a binary) from a list of chunks.</p> @@ -283,7 +283,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="chunks" arity="2"/> + <name name="chunks" arity="2" since=""/> <fsummary>Read selected chunks from a BEAM file or binary.</fsummary> <desc> <p>Reads chunk data for selected chunks references. The order of @@ -293,7 +293,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="chunks" arity="3"/> + <name name="chunks" arity="3" since=""/> <fsummary>Read selected chunks from a BEAM file or binary.</fsummary> <desc> <p>Reads chunk data for selected chunks references. The order of @@ -312,7 +312,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="clear_crypto_key_fun" arity="0"/> + <name name="clear_crypto_key_fun" arity="0" since=""/> <fsummary>Unregister the current crypto key fun.</fsummary> <desc> <p>Unregisters the crypto key fun and terminates the process @@ -327,7 +327,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="cmp" arity="2"/> + <name name="cmp" arity="2" since=""/> <fsummary>Compare two BEAM files.</fsummary> <type name="cmp_rsn"/> <desc> @@ -341,7 +341,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="cmp_dirs" arity="2"/> + <name name="cmp_dirs" arity="2" since=""/> <fsummary>Compare the BEAM files in two directories.</fsummary> <desc> <p>Compares the BEAM files in @@ -359,7 +359,7 @@ io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).</code> </func> <func> - <name name="crypto_key_fun" arity="1"/> + <name name="crypto_key_fun" arity="1" since=""/> <fsummary>Register a fun that provides a crypto key.</fsummary> <type name="crypto_fun"/> <type name="crypto_fun_arg"/> @@ -398,7 +398,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="diff_dirs" arity="2"/> + <name name="diff_dirs" arity="2" since=""/> <fsummary>Compare the BEAM files in two directories.</fsummary> <desc> <p>Compares the BEAM files in two directories as @@ -409,7 +409,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return an English description of a BEAM read error reply. </fsummary> <desc> @@ -422,7 +422,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Information about a BEAM file.</fsummary> <desc> <p>Returns a list containing some information about a BEAM file @@ -449,7 +449,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="md5" arity="1"/> + <name name="md5" arity="1" since=""/> <fsummary>Read the module version of the BEAM file.</fsummary> <desc> <p>Calculates an MD5 redundancy check for the code of the module @@ -458,7 +458,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="strip" arity="1"/> + <name name="strip" arity="1" since=""/> <fsummary>Remove chunks not needed by the loader from a BEAM file. </fsummary> <desc> @@ -470,7 +470,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="strip_files" arity="1"/> + <name name="strip_files" arity="1" since=""/> <fsummary>Removes chunks not needed by the loader from BEAM files. </fsummary> <desc> @@ -483,7 +483,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="strip_release" arity="1"/> + <name name="strip_release" arity="1" since=""/> <fsummary>Remove chunks not needed by the loader from all BEAM files of a release.</fsummary> <desc> @@ -497,7 +497,7 @@ CryptoKeyFun(clear) -> term()</code> </func> <func> - <name name="version" arity="1"/> + <name name="version" arity="1" since=""/> <fsummary>Read the module version of the BEAM file.</fsummary> <desc> <p>Returns the module version or versions. A version is defined by diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml index 6a86d6c7ba..f3d4edd30f 100644 --- a/lib/stdlib/doc/src/binary.xml +++ b/lib/stdlib/doc/src/binary.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>binary.xml</file> </header> - <module>binary</module> + <module since="OTP R14B">binary</module> <modulesummary>Library for handling binary data.</modulesummary> <description> @@ -79,7 +79,7 @@ <funcs> <func> - <name name="at" arity="2"/> + <name name="at" arity="2" since="OTP R14B"/> <fsummary>Return the byte at a specific position in a binary.</fsummary> <desc> <p>Returns the byte at position <c><anno>Pos</anno></c> (zero-based) in @@ -90,7 +90,7 @@ </func> <func> - <name name="bin_to_list" arity="1"/> + <name name="bin_to_list" arity="1" since="OTP R14B"/> <fsummary>Convert a binary to a list of integers.</fsummary> <desc> <p>Same as <c>bin_to_list(<anno>Subject</anno>, {0,byte_size(<anno>Subject</anno>)})</c>.</p> @@ -98,7 +98,7 @@ </func> <func> - <name name="bin_to_list" arity="2"/> + <name name="bin_to_list" arity="2" since="OTP R14B"/> <fsummary>Convert a binary to a list of integers.</fsummary> <desc> <p>Converts <c><anno>Subject</anno></c> to a list of <c>byte()</c>s, each @@ -118,7 +118,7 @@ </func> <func> - <name name="bin_to_list" arity="3"/> + <name name="bin_to_list" arity="3" since="OTP R14B"/> <fsummary>Convert a binary to a list of integers.</fsummary> <desc> <p>Same as<c> bin_to_list(<anno>Subject</anno>, {<anno>Pos</anno>, <anno>Len</anno>})</c>.</p> @@ -126,7 +126,7 @@ </func> <func> - <name name="compile_pattern" arity="1"/> + <name name="compile_pattern" arity="1" since="OTP R14B"/> <fsummary>Precompile a binary search pattern.</fsummary> <desc> <p>Builds an internal structure representing a compilation of a @@ -158,7 +158,7 @@ </func> <func> - <name name="copy" arity="1"/> + <name name="copy" arity="1" since="OTP R14B"/> <fsummary>Create a duplicate of a binary.</fsummary> <desc> <p>Same as <c>copy(<anno>Subject</anno>, 1)</c>.</p> @@ -166,7 +166,7 @@ </func> <func> - <name name="copy" arity="2"/> + <name name="copy" arity="2" since="OTP R14B"/> <fsummary>Duplicate a binary <c>N</c> times and create a new.</fsummary> <desc> <p>Creates a binary with the content of <c><anno>Subject</anno></c> @@ -193,7 +193,7 @@ </func> <func> - <name name="decode_unsigned" arity="1"/> + <name name="decode_unsigned" arity="1" since="OTP R14B"/> <fsummary>Decode a whole binary into an integer of arbitrary size. </fsummary> <desc> @@ -202,7 +202,7 @@ </func> <func> - <name name="decode_unsigned" arity="2"/> + <name name="decode_unsigned" arity="2" since="OTP R14B"/> <fsummary>Decode a whole binary into an integer of arbitrary size. </fsummary> <desc> @@ -219,7 +219,7 @@ </func> <func> - <name name="encode_unsigned" arity="1"/> + <name name="encode_unsigned" arity="1" since="OTP R14B"/> <fsummary>Encode an unsigned integer into the minimal binary.</fsummary> <desc> <p>Same as <c>encode_unsigned(<anno>Unsigned</anno>, big)</c>.</p> @@ -227,7 +227,7 @@ </func> <func> - <name name="encode_unsigned" arity="2"/> + <name name="encode_unsigned" arity="2" since="OTP R14B"/> <fsummary>Encode an unsigned integer into the minimal binary.</fsummary> <desc> <p>Converts a positive integer to the smallest possible @@ -243,7 +243,7 @@ </func> <func> - <name name="first" arity="1"/> + <name name="first" arity="1" since="OTP R14B"/> <fsummary>Return the first byte of a binary.</fsummary> <desc> <p>Returns the first byte of binary <c><anno>Subject</anno></c> as an @@ -253,7 +253,7 @@ </func> <func> - <name name="last" arity="1"/> + <name name="last" arity="1" since="OTP R14B"/> <fsummary>Return the last byte of a binary.</fsummary> <desc> <p>Returns the last byte of binary <c><anno>Subject</anno></c> as an @@ -263,7 +263,7 @@ </func> <func> - <name name="list_to_bin" arity="1"/> + <name name="list_to_bin" arity="1" since="OTP R14B"/> <fsummary>Convert a list of integers and binaries to a binary.</fsummary> <desc> <p>Works exactly as @@ -273,7 +273,7 @@ </func> <func> - <name name="longest_common_prefix" arity="1"/> + <name name="longest_common_prefix" arity="1" since="OTP R14B"/> <fsummary>Return length of longest common prefix for a set of binaries. </fsummary> <desc> @@ -294,7 +294,7 @@ </func> <func> - <name name="longest_common_suffix" arity="1"/> + <name name="longest_common_suffix" arity="1" since="OTP R14B"/> <fsummary>Return length of longest common suffix for a set of binaries. </fsummary> <desc> @@ -315,7 +315,7 @@ </func> <func> - <name name="match" arity="2"/> + <name name="match" arity="2" since="OTP R14B"/> <fsummary>Search for the first match of a pattern in a binary.</fsummary> <desc> <p>Same as <c>match(<anno>Subject</anno>, <anno>Pattern</anno>, [])</c>. @@ -324,7 +324,7 @@ </func> <func> - <name name="match" arity="3"/> + <name name="match" arity="3" since="OTP R14B"/> <fsummary>Search for the first match of a pattern in a binary.</fsummary> <type name="part"/> <desc> @@ -372,7 +372,7 @@ </func> <func> - <name name="matches" arity="2"/> + <name name="matches" arity="2" since="OTP R14B"/> <fsummary>Search for all matches of a pattern in a binary.</fsummary> <desc> <p>Same as <c>matches(<anno>Subject</anno>, <anno>Pattern</anno>, [])</c>. @@ -381,7 +381,7 @@ </func> <func> - <name name="matches" arity="3"/> + <name name="matches" arity="3" since="OTP R14B"/> <fsummary>Search for all matches of a pattern in a binary.</fsummary> <type name="part"/> <desc> @@ -425,7 +425,7 @@ </func> <func> - <name name="part" arity="2"/> + <name name="part" arity="2" since="OTP R14B"/> <fsummary>Extract a part of a binary.</fsummary> <desc> <p>Extracts the part of binary <c><anno>Subject</anno></c> described by @@ -453,7 +453,7 @@ </func> <func> - <name name="part" arity="3"/> + <name name="part" arity="3" since="OTP R14B"/> <fsummary>Extract a part of a binary.</fsummary> <desc> <p>Same as <c>part(<anno>Subject</anno>, {<anno>Pos</anno>, @@ -462,7 +462,7 @@ </func> <func> - <name name="referenced_byte_size" arity="1"/> + <name name="referenced_byte_size" arity="1" since="OTP R14B"/> <fsummary>Determine the size of the binary pointed out by a subbinary. </fsummary> <desc> @@ -525,7 +525,7 @@ store(Binary, GBSet) -> </func> <func> - <name name="replace" arity="3"/> + <name name="replace" arity="3" since="OTP R14B"/> <fsummary>Replace bytes in a binary according to a pattern.</fsummary> <desc> <p>Same as <c>replace(<anno>Subject</anno>, <anno>Pattern</anno>, <anno>Replacement</anno>,[])</c>.</p> @@ -533,7 +533,7 @@ store(Binary, GBSet) -> </func> <func> - <name name="replace" arity="4"/> + <name name="replace" arity="4" since="OTP R14B"/> <fsummary>Replace bytes in a binary according to a pattern.</fsummary> <type_desc variable="OnePos">An integer() =< byte_size(<anno>Replacement</anno>) </type_desc> @@ -575,7 +575,7 @@ store(Binary, GBSet) -> </func> <func> - <name name="split" arity="2"/> + <name name="split" arity="2" since="OTP R14B"/> <fsummary>Split a binary according to a pattern.</fsummary> <desc> <p>Same as <c>split(<anno>Subject</anno>, <anno>Pattern</anno>, @@ -584,7 +584,7 @@ store(Binary, GBSet) -> </func> <func> - <name name="split" arity="3"/> + <name name="split" arity="3" since="OTP R14B"/> <fsummary>Split a binary according to a pattern.</fsummary> <desc> <p>Splits <c><anno>Subject</anno></c> into a list of binaries based on diff --git a/lib/stdlib/doc/src/c.xml b/lib/stdlib/doc/src/c.xml index b6cb6f5aae..29edc373c7 100644 --- a/lib/stdlib/doc/src/c.xml +++ b/lib/stdlib/doc/src/c.xml @@ -28,7 +28,7 @@ <date>1996-10-30</date> <rev>B</rev> </header> - <module>c</module> + <module since="">c</module> <modulesummary>Command interface module.</modulesummary> <description> <p>This module enables users to enter the short form of @@ -41,7 +41,7 @@ <funcs> <func> - <name name="bt" arity="1"/> + <name name="bt" arity="1" since=""/> <fsummary>Stack backtrace for a process.</fsummary> <desc> <p>Stack backtrace for a process. Equivalent to @@ -50,9 +50,9 @@ </func> <func> - <name name="c" arity="1"/> - <name name="c" arity="2"/> - <name name="c" arity="3"/> + <name name="c" arity="1" since=""/> + <name name="c" arity="2" since=""/> + <name name="c" arity="3" since="OTP 20.0"/> <fsummary>Compile and load a file or module.</fsummary> <desc> <p>Compiles and then purges and loads the code for a module. @@ -80,7 +80,7 @@ </func> <func> - <name name="cd" arity="1"/> + <name name="cd" arity="1" since=""/> <fsummary>Change working directory.</fsummary> <desc> <p>Changes working directory to <c><anno>Dir</anno></c>, which can be a @@ -94,7 +94,7 @@ </func> <func> - <name name="erlangrc" arity="1"/> + <name name="erlangrc" arity="1" since="OTP 21.0"/> <fsummary>Load an erlang resource file.</fsummary> <desc> <p>Search <c>PathList</c> and load <c>.erlang</c> resource file if @@ -103,7 +103,7 @@ </func> <func> - <name name="flush" arity="0"/> + <name name="flush" arity="0" since=""/> <fsummary>Flush any messages sent to the shell.</fsummary> <desc> <p>Flushes any messages sent to the shell.</p> @@ -111,7 +111,7 @@ </func> <func> - <name name="help" arity="0"/> + <name name="help" arity="0" since=""/> <fsummary>Help information.</fsummary> <desc> <p>Displays help information: all valid shell internal commands, @@ -120,8 +120,8 @@ </func> <func> - <name name="i" arity="0"/> - <name name="ni" arity="0"/> + <name name="i" arity="0" since=""/> + <name name="ni" arity="0" since=""/> <fsummary>System information.</fsummary> <desc> <p><c>i/0</c> displays system information, listing @@ -131,7 +131,7 @@ </func> <func> - <name name="i" arity="3"/> + <name name="i" arity="3" since=""/> <fsummary>Information about pid <X.Y.Z>.</fsummary> <desc> <p>Displays information about a process, Equivalent to @@ -141,7 +141,7 @@ </func> <func> - <name name="l" arity="1"/> + <name name="l" arity="1" since=""/> <fsummary>Load or reload a module.</fsummary> <desc> <p>Purges and loads, or reloads, a module by calling @@ -154,7 +154,7 @@ </func> <func> - <name>lc(Files) -> ok</name> + <name since="">lc(Files) -> ok</name> <fsummary>Compile a list of files.</fsummary> <type> <v>Files = [File]</v> @@ -171,7 +171,7 @@ </func> <func> - <name name="lm" arity="0"/> + <name name="lm" arity="0" since="OTP 20.0"/> <fsummary>Loads all modified modules.</fsummary> <desc> <p>Reloads all currently loaded modules that have changed on disk (see <c>mm()</c>). @@ -180,7 +180,7 @@ </func> <func> - <name name="ls" arity="0"/> + <name name="ls" arity="0" since=""/> <fsummary>List files in the current directory.</fsummary> <desc> <p>Lists files in the current directory.</p> @@ -188,7 +188,7 @@ </func> <func> - <name name="ls" arity="1"/> + <name name="ls" arity="1" since=""/> <fsummary>List files in a directory or a single file.</fsummary> <desc> <p>Lists files in directory <c><anno>Dir</anno></c> or, if <c>Dir</c> @@ -197,7 +197,7 @@ </func> <func> - <name name="m" arity="0"/> + <name name="m" arity="0" since=""/> <fsummary>Which modules are loaded.</fsummary> <desc> <p>Displays information about the loaded modules, including @@ -206,7 +206,7 @@ </func> <func> - <name name="m" arity="1"/> + <name name="m" arity="1" since=""/> <fsummary>Information about a module.</fsummary> <desc> <p>Displays information about <c><anno>Module</anno></c>.</p> @@ -214,7 +214,7 @@ </func> <func> - <name name="mm" arity="0"/> + <name name="mm" arity="0" since="OTP 20.0"/> <fsummary>Lists all modified modules.</fsummary> <desc> <p>Lists all modified modules. Shorthand for @@ -223,7 +223,7 @@ </func> <func> - <name name="memory" arity="0"/> + <name name="memory" arity="0" since=""/> <fsummary>Memory allocation information.</fsummary> <desc> <p>Memory allocation information. Equivalent to @@ -232,8 +232,8 @@ </func> <func> - <name name="memory" arity="1" clause_i="1"/> - <name name="memory" arity="1" clause_i="2"/> + <name name="memory" arity="1" clause_i="1" since=""/> + <name name="memory" arity="1" clause_i="2" since=""/> <fsummary>Memory allocation information.</fsummary> <desc> <p>Memory allocation information. Equivalent to @@ -242,8 +242,8 @@ </func> <func> - <name name="nc" arity="1"/> - <name name="nc" arity="2"/> + <name name="nc" arity="1" since=""/> + <name name="nc" arity="2" since=""/> <fsummary>Compile and load code in a file on all nodes.</fsummary> <desc> <p>Compiles and then loads the code for a file on all nodes. @@ -255,7 +255,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name name="nl" arity="1"/> + <name name="nl" arity="1" since=""/> <fsummary>Load module on all nodes.</fsummary> <desc> <p>Loads <c><anno>Module</anno></c> on all nodes.</p> @@ -263,7 +263,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name name="pid" arity="3"/> + <name name="pid" arity="3" since=""/> <fsummary>Convert <c>X,Y,Z</c> to a pid.</fsummary> <desc> <p>Converts <c><anno>X</anno></c>, <c><anno>Y</anno></c>, @@ -273,7 +273,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name name="pwd" arity="0"/> + <name name="pwd" arity="0" since=""/> <fsummary>Print working directory.</fsummary> <desc> <p>Prints the name of the working directory.</p> @@ -281,7 +281,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name name="q" arity="0"/> + <name name="q" arity="0" since=""/> <fsummary>Quit - shorthand for <c>init:stop()</c>.</fsummary> <desc> <p>This function is shorthand for <c>init:stop()</c>, that is, @@ -290,8 +290,8 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name name="regs" arity="0"/> - <name name="nregs" arity="0"/> + <name name="regs" arity="0" since=""/> + <name name="nregs" arity="0" since=""/> <fsummary>Information about registered processes.</fsummary> <desc> <p><c>regs/0</c> displays information about all registered @@ -301,7 +301,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name name="uptime" arity="0"/> + <name name="uptime" arity="0" since="OTP 18.0"/> <fsummary>Print node uptime.</fsummary> <desc> <p>Prints the node uptime (as specified by @@ -310,7 +310,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name>xm(ModSpec) -> void()</name> + <name since="">xm(ModSpec) -> void()</name> <fsummary>Cross-reference check a module.</fsummary> <type> <v>ModSpec = Module | Filename</v> @@ -325,7 +325,7 @@ compile:file(<anno>File</anno>, <anno>Options</anno> ++ [report_errors, report_w </func> <func> - <name>y(File) -> YeccRet</name> + <name since="">y(File) -> YeccRet</name> <fsummary>Generate an LALR-1 parser.</fsummary> <type> <v>File = name()</v> @@ -344,7 +344,7 @@ yecc:file(File)</code> </func> <func> - <name>y(File, Options) -> YeccRet</name> + <name since="">y(File, Options) -> YeccRet</name> <fsummary>Generate an LALR-1 parser.</fsummary> <type> <v>File = name()</v> diff --git a/lib/stdlib/doc/src/calendar.xml b/lib/stdlib/doc/src/calendar.xml index 6b4fa7f98a..518a085c89 100644 --- a/lib/stdlib/doc/src/calendar.xml +++ b/lib/stdlib/doc/src/calendar.xml @@ -28,7 +28,7 @@ <date>1996-11-05</date> <rev>B</rev> </header> - <module>calendar</module> + <module since="">calendar</module> <modulesummary>Local and universal time, day of the week, date and time conversions.</modulesummary> <description> @@ -128,8 +128,8 @@ <funcs> <func> - <name name="date_to_gregorian_days" arity="1"/> - <name name="date_to_gregorian_days" arity="3"/> + <name name="date_to_gregorian_days" arity="1" since=""/> + <name name="date_to_gregorian_days" arity="3" since=""/> <fsummary>Compute the number of days from year 0 up to the specified date.</fsummary> <type variable="Date" name_i="1"/> @@ -143,7 +143,7 @@ </func> <func> - <name name="datetime_to_gregorian_seconds" arity="1"/> + <name name="datetime_to_gregorian_seconds" arity="1" since=""/> <fsummary>Compute the number of seconds from year 0 up to the specified date and time.</fsummary> <desc> @@ -153,8 +153,8 @@ </func> <func> - <name name="day_of_the_week" arity="1"/> - <name name="day_of_the_week" arity="3"/> + <name name="day_of_the_week" arity="1" since=""/> + <name name="day_of_the_week" arity="3" since=""/> <fsummary>Compute the day of the week.</fsummary> <type variable="Date" name_i="1"/> <type variable="Year"/> @@ -169,7 +169,7 @@ </func> <func> - <name name="gregorian_days_to_date" arity="1"/> + <name name="gregorian_days_to_date" arity="1" since=""/> <fsummary>Compute the date from the number of gregorian days.</fsummary> <desc> <p>Computes the date from the specified number of gregorian days.</p> @@ -177,7 +177,7 @@ </func> <func> - <name name="gregorian_seconds_to_datetime" arity="1"/> + <name name="gregorian_seconds_to_datetime" arity="1" since=""/> <fsummary>Compute the date and time from the number of gregorian seconds. </fsummary> <desc> @@ -187,7 +187,7 @@ </func> <func> - <name name="is_leap_year" arity="1"/> + <name name="is_leap_year" arity="1" since=""/> <fsummary>Check if the year is a leap year.</fsummary> <desc> <p>Checks if the specified year is a leap year.</p> @@ -195,7 +195,7 @@ </func> <func> - <name name="iso_week_number" arity="0"/> + <name name="iso_week_number" arity="0" since="OTP R14B02"/> <fsummary>Compute the ISO week number for the actual date.</fsummary> <desc> <p>Returns tuple <c>{Year, WeekNum}</c> representing @@ -206,7 +206,7 @@ </func> <func> - <name name="iso_week_number" arity="1"/> + <name name="iso_week_number" arity="1" since="OTP R14B02"/> <fsummary>Compute the ISO week number for the specified date.</fsummary> <desc> <p>Returns tuple <c>{Year, WeekNum}</c> representing @@ -215,7 +215,7 @@ </func> <func> - <name name="last_day_of_the_month" arity="2"/> + <name name="last_day_of_the_month" arity="2" since=""/> <fsummary>Compute the number of days in a month.</fsummary> <desc> <p>Computes the number of days in a month.</p> @@ -223,7 +223,7 @@ </func> <func> - <name name="local_time" arity="0"/> + <name name="local_time" arity="0" since=""/> <fsummary>Compute local time.</fsummary> <desc> <p>Returns the local time reported by @@ -232,7 +232,7 @@ </func> <func> - <name name="local_time_to_universal_time" arity="1"/> + <name name="local_time_to_universal_time" arity="1" since=""/> <fsummary>Convert from local time to universal time (deprecated). </fsummary> <desc> @@ -253,7 +253,7 @@ </func> <func> - <name name="local_time_to_universal_time_dst" arity="1"/> + <name name="local_time_to_universal_time_dst" arity="1" since=""/> <fsummary>Convert from local time to universal time(s).</fsummary> <desc> <p>Converts from local time to Universal Coordinated Time (UTC). @@ -285,7 +285,7 @@ </func> <func> - <name name="now_to_datetime" arity="1"/> + <name name="now_to_datetime" arity="1" since=""/> <fsummary>Convert now to date and time.</fsummary> <desc> <p>Returns Universal Coordinated Time (UTC) @@ -296,7 +296,7 @@ </func> <func> - <name name="now_to_local_time" arity="1"/> + <name name="now_to_local_time" arity="1" since=""/> <fsummary>Convert now to local date and time.</fsummary> <desc> <p>Returns local date and time converted from the return value from @@ -306,7 +306,7 @@ </func> <func> - <name name="now_to_universal_time" arity="1"/> + <name name="now_to_universal_time" arity="1" since=""/> <fsummary>Convert now to date and time.</fsummary> <desc> <p>Returns Universal Coordinated Time (UTC) @@ -317,8 +317,8 @@ </func> <func> - <name name="rfc3339_to_system_time" arity="1"/> - <name name="rfc3339_to_system_time" arity="2"/> + <name name="rfc3339_to_system_time" arity="1" since="OTP 21.0"/> + <name name="rfc3339_to_system_time" arity="2" since="OTP 21.0"/> <fsummary>Convert from RFC 3339 timestamp to system time.</fsummary> <type name="rfc3339_string"/> <type name="rfc3339_time_unit"/> @@ -343,7 +343,7 @@ </func> <func> - <name name="seconds_to_daystime" arity="1"/> + <name name="seconds_to_daystime" arity="1" since=""/> <fsummary>Compute days and time from seconds.</fsummary> <desc> <p>Converts a specified number of seconds into days, hours, minutes, @@ -354,7 +354,7 @@ </func> <func> - <name name="seconds_to_time" arity="1"/> + <name name="seconds_to_time" arity="1" since=""/> <fsummary>Compute time from seconds.</fsummary> <type name="secs_per_day"/> <desc> @@ -365,7 +365,7 @@ </func> <func> - <name name="system_time_to_local_time" arity="2"/> + <name name="system_time_to_local_time" arity="2" since="OTP 21.0"/> <fsummary>Convert system time to local date and time.</fsummary> <desc> <p>Converts a specified system time into local date and time.</p> @@ -373,8 +373,8 @@ </func> <func> - <name name="system_time_to_rfc3339" arity="1"/> - <name name="system_time_to_rfc3339" arity="2"/> + <name name="system_time_to_rfc3339" arity="1" since="OTP 21.0"/> + <name name="system_time_to_rfc3339" arity="2" since="OTP 21.0"/> <fsummary>Convert from system to RFC 3339 timestamp.</fsummary> <type name="offset"/> <type name="rfc3339_string"/> @@ -403,7 +403,11 @@ default is <c>second</c>. If some other unit is given (<c>millisecond</c>, <c>microsecond</c>, or <c>nanosecond</c>), the formatted string includes a - fraction of a second.</p> + fraction of a second. The number of fractional second + digits is three, six, or nine depending on what time unit + is chosen. Notice that trailing zeros are not removed from + the fraction. + </p> </item> </taglist> <pre> @@ -422,7 +426,7 @@ </func> <func> - <name name="system_time_to_universal_time" arity="2"/> + <name name="system_time_to_universal_time" arity="2" since="OTP 21.0"/> <fsummary>Convert system time to universal date and time.</fsummary> <desc> <p>Converts a specified system time into universal date and time.</p> @@ -430,7 +434,7 @@ </func> <func> - <name name="time_difference" arity="2"/> + <name name="time_difference" arity="2" since=""/> <fsummary>Compute the difference between two times (deprecated). </fsummary> <desc> @@ -445,7 +449,7 @@ </func> <func> - <name name="time_to_seconds" arity="1"/> + <name name="time_to_seconds" arity="1" since=""/> <fsummary>Compute the number of seconds since midnight up to the specified time.</fsummary> <type name="secs_per_day"/> @@ -456,7 +460,7 @@ </func> <func> - <name name="universal_time" arity="0"/> + <name name="universal_time" arity="0" since=""/> <fsummary>Compute universal time.</fsummary> <desc> <p>Returns the Universal Coordinated Time (UTC) @@ -466,7 +470,7 @@ </func> <func> - <name name="universal_time_to_local_time" arity="1"/> + <name name="universal_time_to_local_time" arity="1" since=""/> <fsummary>Convert from universal time to local time.</fsummary> <desc> <p>Converts from Universal Coordinated Time (UTC) to local time. @@ -476,8 +480,8 @@ </func> <func> - <name name="valid_date" arity="1"/> - <name name="valid_date" arity="3"/> + <name name="valid_date" arity="1" since=""/> + <name name="valid_date" arity="3" since=""/> <fsummary>Check if a date is valid</fsummary> <type variable="Date" name_i="1"/> <type variable="Year"/> diff --git a/lib/stdlib/doc/src/dets.xml b/lib/stdlib/doc/src/dets.xml index eb6e32aecf..8e4e002000 100644 --- a/lib/stdlib/doc/src/dets.xml +++ b/lib/stdlib/doc/src/dets.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>dets.xml</file> </header> - <module>dets</module> + <module since="">dets</module> <modulesummary>A disk-based term storage.</modulesummary> <description> <p>This module provides a term storage on file. The @@ -188,7 +188,7 @@ <funcs> <func> - <name name="all" arity="0"/> + <name name="all" arity="0" since=""/> <fsummary>Return a list of the names of all open Dets tables on this node.</fsummary> <desc> @@ -197,7 +197,7 @@ </func> <func> - <name name="bchunk" arity="2"/> + <name name="bchunk" arity="2" since=""/> <fsummary>Return a chunk of objects stored in a Dets table. </fsummary> <desc> @@ -227,7 +227,7 @@ </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close a Dets table.</fsummary> <desc> <p>Closes a table. Only processes that have opened a table are @@ -239,7 +239,7 @@ </func> <func> - <name name="delete" arity="2"/> + <name name="delete" arity="2" since=""/> <fsummary>Delete all objects with a specified key from a Dets table.</fsummary> <desc> @@ -249,7 +249,7 @@ </func> <func> - <name name="delete_all_objects" arity="1"/> + <name name="delete_all_objects" arity="1" since=""/> <fsummary>Delete all objects from a Dets table.</fsummary> <desc> <p>Deletes all objects from a table in almost constant time. @@ -259,7 +259,7 @@ </func> <func> - <name name="delete_object" arity="2"/> + <name name="delete_object" arity="2" since=""/> <fsummary>Delete a specified object from a Dets table.</fsummary> <desc> <p>Deletes all instances of a specified object from a table. If a @@ -270,7 +270,7 @@ </func> <func> - <name name="first" arity="1"/> + <name name="first" arity="1" since=""/> <fsummary>Return the first key stored in a Dets table.</fsummary> <desc> <p>Returns the first key stored in table <c><anno>Name</anno></c> @@ -295,8 +295,8 @@ </func> <func> - <name name="foldl" arity="3"/> - <name name="foldr" arity="3"/> + <name name="foldl" arity="3" since=""/> + <name name="foldr" arity="3" since=""/> <fsummary>Fold a function over a Dets table.</fsummary> <desc> <p>Calls <c><anno>Function</anno></c> on successive elements of @@ -309,7 +309,7 @@ </func> <func> - <name name="from_ets" arity="2"/> + <name name="from_ets" arity="2" since=""/> <fsummary>Replace the objects of a Dets table with the objects of an ETS table.</fsummary> <desc> @@ -322,7 +322,7 @@ </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Return information about a Dets table.</fsummary> <desc> <p>Returns information about table <c><anno>Name</anno></c> @@ -354,7 +354,7 @@ </func> <func> - <name name="info" arity="2"/> + <name name="info" arity="2" since=""/> <fsummary>Return the information associated with a specified item for a Dets table.</fsummary> <desc> @@ -455,8 +455,8 @@ </func> <func> - <name name="init_table" arity="2"/> - <name name="init_table" arity="3"/> + <name name="init_table" arity="2" since=""/> + <name name="init_table" arity="3" since=""/> <fsummary>Replace all objects of a Dets table.</fsummary> <desc> <p>Replaces the existing objects of table <c><anno>Name</anno></c> @@ -516,7 +516,7 @@ </func> <func> - <name name="insert" arity="2"/> + <name name="insert" arity="2" since=""/> <fsummary>Insert one or more objects into a Dets table.</fsummary> <desc> <p>Inserts one or more objects into the table <c><anno>Name</anno></c>. @@ -527,7 +527,7 @@ </func> <func> - <name name="insert_new" arity="2"/> + <name name="insert_new" arity="2" since=""/> <fsummary>Insert one or more objects into a Dets table.</fsummary> <desc> <p>Inserts one or more objects into table <c><anno>Name</anno></c>. @@ -539,7 +539,7 @@ </func> <func> - <name name="is_compatible_bchunk_format" arity="2"/> + <name name="is_compatible_bchunk_format" arity="2" since=""/> <fsummary>Test compatibility of chunk data of a table.</fsummary> <desc> <p>Returns <c>true</c> if it would be possible to initialize @@ -554,7 +554,7 @@ </func> <func> - <name name="is_dets_file" arity="1"/> + <name name="is_dets_file" arity="1" since=""/> <fsummary>Test for a Dets table.</fsummary> <desc> <p>Returns <c>true</c> if file <c><anno>Filename</anno></c> @@ -563,7 +563,7 @@ </func> <func> - <name name="lookup" arity="2"/> + <name name="lookup" arity="2" since=""/> <fsummary>Return all objects with a specified key stored in a Dets table.</fsummary> <desc> @@ -590,7 +590,7 @@ ok </func> <func> - <name name="match" arity="1"/> + <name name="match" arity="1" since=""/> <fsummary>Match a chunk of objects stored in a Dets table and return a list of variable bindings.</fsummary> <desc> @@ -606,7 +606,7 @@ ok </func> <func> - <name name="match" arity="2"/> + <name name="match" arity="2" since=""/> <fsummary>Match the objects stored in a Dets table and return a list of variable bindings.</fsummary> <desc> @@ -622,7 +622,7 @@ ok </func> <func> - <name name="match" arity="3"/> + <name name="match" arity="3" since=""/> <fsummary>Match the first chunk of objects stored in a Dets table and return a list of variable bindings.</fsummary> <desc> @@ -654,7 +654,7 @@ ok </func> <func> - <name name="match_delete" arity="2"/> + <name name="match_delete" arity="2" since=""/> <fsummary>Delete all objects that match a given pattern from a Dets table.</fsummary> <desc> @@ -667,7 +667,7 @@ ok </func> <func> - <name name="match_object" arity="1"/> + <name name="match_object" arity="1" since=""/> <fsummary>Match a chunk of objects stored in a Dets table and return a list of objects.</fsummary> <desc> @@ -683,7 +683,7 @@ ok </func> <func> - <name name="match_object" arity="2"/> + <name name="match_object" arity="2" since=""/> <fsummary>Match the objects stored in a Dets table and return a list of objects.</fsummary> <desc> @@ -702,7 +702,7 @@ ok </func> <func> - <name name="match_object" arity="3"/> + <name name="match_object" arity="3" since=""/> <fsummary>Match the first chunk of objects stored in a Dets table and return a list of objects.</fsummary> <desc> @@ -735,7 +735,7 @@ ok </func> <func> - <name name="member" arity="2"/> + <name name="member" arity="2" since=""/> <fsummary>Test for occurrence of a key in a Dets table.</fsummary> <desc> <p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>, @@ -746,7 +746,7 @@ ok </func> <func> - <name name="next" arity="2"/> + <name name="next" arity="2" since=""/> <fsummary>Return the next key in a Dets table.</fsummary> <desc> <p>Returns either the key following <c><anno>Key1</anno></c> in table @@ -760,7 +760,7 @@ ok </func> <func> - <name name="open_file" arity="1"/> + <name name="open_file" arity="1" since=""/> <fsummary>Open an existing Dets table.</fsummary> <desc> <p>Opens an existing table. If the table is not properly closed, @@ -770,7 +770,7 @@ ok </func> <func> - <name name="open_file" arity="2"/> + <name name="open_file" arity="2" since=""/> <fsummary>Open a Dets table.</fsummary> <desc> <p>Opens a table. An empty Dets table is created if no file @@ -872,7 +872,7 @@ ok </func> <func> - <name name="pid2name" arity="1"/> + <name name="pid2name" arity="1" since=""/> <fsummary>Return the name of the Dets table handled by a pid.</fsummary> <desc> <p>Returns the table name given the pid of a process @@ -883,7 +883,7 @@ ok </func> <func> - <name name="repair_continuation" arity="2"/> + <name name="repair_continuation" arity="2" since=""/> <fsummary>Repair a continuation from <c>select/1</c> or <c>select/3</c>. </fsummary> <desc> @@ -917,7 +917,7 @@ ok </func> <func> - <name name="safe_fixtable" arity="2"/> + <name name="safe_fixtable" arity="2" since=""/> <fsummary>Fix a Dets table for safe traversal.</fsummary> <desc> <p>If <c><anno>Fix</anno></c> is <c>true</c>, table @@ -945,7 +945,7 @@ ok </func> <func> - <name name="select" arity="1"/> + <name name="select" arity="1" since=""/> <fsummary>Apply a match specification to some objects stored in a Dets table.</fsummary> <desc> @@ -962,7 +962,7 @@ ok </func> <func> - <name name="select" arity="2"/> + <name name="select" arity="2" since=""/> <fsummary>Apply a match specification to all objects stored in a Dets table.</fsummary> <desc> @@ -984,7 +984,7 @@ ok </func> <func> - <name name="select" arity="3"/> + <name name="select" arity="3" since=""/> <fsummary>Apply a match specification to the first chunk of objects stored in a Dets table.</fsummary> <desc> @@ -1019,7 +1019,7 @@ ok </func> <func> - <name name="select_delete" arity="2"/> + <name name="select_delete" arity="2" since=""/> <fsummary>Delete all objects that match a given pattern from a Dets table.</fsummary> <desc> @@ -1036,7 +1036,7 @@ ok </func> <func> - <name name="slot" arity="2"/> + <name name="slot" arity="2" since=""/> <fsummary>Return the list of objects associated with a slot of a Dets table.</fsummary> <desc> @@ -1049,7 +1049,7 @@ ok </func> <func> - <name name="sync" arity="1"/> + <name name="sync" arity="1" since=""/> <fsummary>Ensure that all updates made to a Dets table are written to disk.</fsummary> <desc> @@ -1064,8 +1064,8 @@ ok </func> <func> - <name name="table" arity="1"/> - <name name="table" arity="2"/> + <name name="table" arity="1" since=""/> + <name name="table" arity="2" since=""/> <fsummary>Return a QLC query handle.</fsummary> <desc> <p>Returns a Query List @@ -1140,7 +1140,7 @@ true</pre> </func> <func> - <name name="to_ets" arity="2"/> + <name name="to_ets" arity="2" since=""/> <fsummary>Insert all objects of a Dets table into an ETS table.</fsummary> <desc> @@ -1153,7 +1153,7 @@ true</pre> </func> <func> - <name name="traverse" arity="2"/> + <name name="traverse" arity="2" since=""/> <fsummary>Apply a function to all or some objects stored in a Dets table.</fsummary> <desc> @@ -1192,7 +1192,7 @@ fun(X) -> {continue, X} end.</pre> </func> <func> - <name name="update_counter" arity="3"/> + <name name="update_counter" arity="3" since=""/> <fsummary>Update a counter object stored in a Dets table. </fsummary> <desc> diff --git a/lib/stdlib/doc/src/dict.xml b/lib/stdlib/doc/src/dict.xml index c229a18721..95a98cef12 100644 --- a/lib/stdlib/doc/src/dict.xml +++ b/lib/stdlib/doc/src/dict.xml @@ -28,7 +28,7 @@ <date>1997-01-15</date> <rev>B</rev> </header> - <module>dict</module> + <module since="">dict</module> <modulesummary>Key-value dictionary.</modulesummary> <description> <p>This module provides a <c>Key</c>-<c>Value</c> dictionary. @@ -55,7 +55,7 @@ <funcs> <func> - <name name="append" arity="3"/> + <name name="append" arity="3" since=""/> <fsummary>Append a value to keys in a dictionary.</fsummary> <desc> <p>Appends a new <c><anno>Value</anno></c> to the current list @@ -65,7 +65,7 @@ </func> <func> - <name name="append_list" arity="3"/> + <name name="append_list" arity="3" since=""/> <fsummary>Append new values to keys in a dictionary.</fsummary> <desc> <p>Appends a list of values <c><anno>ValList</anno></c> to @@ -77,7 +77,7 @@ </func> <func> - <name name="erase" arity="2"/> + <name name="erase" arity="2" since=""/> <fsummary>Erase a key from a dictionary.</fsummary> <desc> <p>Erases all items with a given key from a dictionary.</p> @@ -85,7 +85,7 @@ </func> <func> - <name name="fetch" arity="2"/> + <name name="fetch" arity="2" since=""/> <fsummary>Look up values in a dictionary.</fsummary> <desc> <p>Returns the value associated with <c><anno>Key</anno></c> @@ -98,7 +98,7 @@ </func> <func> - <name name="fetch_keys" arity="1"/> + <name name="fetch_keys" arity="1" since=""/> <fsummary>Return all keys in a dictionary.</fsummary> <desc> <p>Returns a list of all keys in dictionary <c>Dict</c>.</p> @@ -106,7 +106,7 @@ </func> <func> - <name name="take" arity="2"/> + <name name="take" arity="2" since="OTP 20.0"/> <fsummary>Return value and new dictionary without element with this value.</fsummary> <desc> <p>This function returns value from dictionary and a @@ -116,7 +116,7 @@ </func> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Select elements that satisfy a predicate.</fsummary> <desc> <p><c><anno>Dict2</anno></c> is a dictionary of all keys and values in @@ -127,7 +127,7 @@ </func> <func> - <name name="find" arity="2"/> + <name name="find" arity="2" since=""/> <fsummary>Search for a key in a dictionary.</fsummary> <desc> <p>Searches for a key in dictionary <c>Dict</c>. Returns @@ -139,7 +139,7 @@ </func> <func> - <name name="fold" arity="3"/> + <name name="fold" arity="3" since=""/> <fsummary>Fold a function over a dictionary.</fsummary> <desc> <p>Calls <c><anno>Fun</anno></c> on successive keys and values of @@ -153,7 +153,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Convert a list of pairs to a dictionary.</fsummary> <desc> <p>Converts the <c><anno>Key</anno></c>-<c><anno>Value</anno></c> list @@ -162,7 +162,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since="OTP 17.0"/> <fsummary>Return <c>true</c> if the dictionary is empty.</fsummary> <desc> <p>Returns <c>true</c> if dictionary <c><anno>Dict</anno></c> has no @@ -171,7 +171,7 @@ </func> <func> - <name name="is_key" arity="2"/> + <name name="is_key" arity="2" since=""/> <fsummary>Test if a key is in a dictionary.</fsummary> <desc> <p>Tests if <c><anno>Key</anno></c> is contained in @@ -180,7 +180,7 @@ </func> <func> - <name name="map" arity="2"/> + <name name="map" arity="2" since=""/> <fsummary>Map a function over a dictionary.</fsummary> <desc> <p>Calls <c><anno>Fun</anno></c> on successive keys and values @@ -190,7 +190,7 @@ </func> <func> - <name name="merge" arity="3"/> + <name name="merge" arity="3" since=""/> <fsummary>Merge two dictionaries.</fsummary> <desc> <p>Merges two dictionaries, <c><anno>Dict1</anno></c> and @@ -209,7 +209,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Create a dictionary.</fsummary> <desc> <p>Creates a new dictionary.</p> @@ -217,7 +217,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Return the number of elements in a dictionary.</fsummary> <desc> <p>Returns the number of elements in dictionary @@ -226,7 +226,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="store" arity="3"/> + <name name="store" arity="3" since=""/> <fsummary>Store a value in a dictionary.</fsummary> <desc> <p>Stores a <c><anno>Key</anno></c>-<c><anno>Value</anno></c> pair in @@ -237,7 +237,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert a dictionary to a list of pairs.</fsummary> <desc> <p>Converts dictionary <c>Dict</c> to a list representation.</p> @@ -245,7 +245,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="update" arity="3"/> + <name name="update" arity="3" since=""/> <fsummary>Update a value in a dictionary.</fsummary> <desc> <p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> on @@ -255,7 +255,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="update" arity="4"/> + <name name="update" arity="4" since=""/> <fsummary>Update a value in a dictionary.</fsummary> <desc> <p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> on @@ -269,7 +269,7 @@ append(Key, Val, D) -> </func> <func> - <name name="update_counter" arity="3"/> + <name name="update_counter" arity="3" since=""/> <fsummary>Increment a value in a dictionary.</fsummary> <desc> <p>Adds <c><anno>Increment</anno></c> to the value associated with diff --git a/lib/stdlib/doc/src/digraph.xml b/lib/stdlib/doc/src/digraph.xml index a5252b443b..cf2c0844c9 100644 --- a/lib/stdlib/doc/src/digraph.xml +++ b/lib/stdlib/doc/src/digraph.xml @@ -32,7 +32,7 @@ <rev>C</rev> <file>digraph.xml</file> </header> - <module>digraph</module> + <module since="">digraph</module> <modulesummary>Directed graphs.</modulesummary> <description> <p>This module provides a version of labeled @@ -144,9 +144,9 @@ <funcs> <func> - <name name="add_edge" arity="3"/> - <name name="add_edge" arity="4"/> - <name name="add_edge" arity="5"/> + <name name="add_edge" arity="3" since=""/> + <name name="add_edge" arity="4" since=""/> + <name name="add_edge" arity="5" since=""/> <fsummary>Add an edge to a digraph.</fsummary> <type name="add_edge_err_rsn"/> <desc> @@ -183,9 +183,9 @@ </func> <func> - <name name="add_vertex" arity="1"/> - <name name="add_vertex" arity="2"/> - <name name="add_vertex" arity="3"/> + <name name="add_vertex" arity="1" since=""/> + <name name="add_vertex" arity="2" since=""/> + <name name="add_vertex" arity="3" since=""/> <fsummary>Add or modify a vertex of a digraph.</fsummary> <desc> <p><c>add_vertex/3</c> creates (or modifies) vertex @@ -204,7 +204,7 @@ </func> <func> - <name name="del_edge" arity="2"/> + <name name="del_edge" arity="2" since=""/> <fsummary>Delete an edge from a digraph.</fsummary> <desc> <p>Deletes edge <c><anno>E</anno></c> from digraph @@ -213,7 +213,7 @@ </func> <func> - <name name="del_edges" arity="2"/> + <name name="del_edges" arity="2" since=""/> <fsummary>Delete edges from a digraph.</fsummary> <desc> <p>Deletes the edges in list <c><anno>Edges</anno></c> from digraph @@ -222,7 +222,7 @@ </func> <func> - <name name="del_path" arity="3"/> + <name name="del_path" arity="3" since=""/> <fsummary>Delete paths from a digraph.</fsummary> <desc> <p>Deletes edges from digraph <c><anno>G</anno></c> until there are no @@ -252,7 +252,7 @@ </func> <func> - <name name="del_vertex" arity="2"/> + <name name="del_vertex" arity="2" since=""/> <fsummary>Delete a vertex from a digraph.</fsummary> <desc> <p>Deletes vertex <c><anno>V</anno></c> from digraph @@ -265,7 +265,7 @@ </func> <func> - <name name="del_vertices" arity="2"/> + <name name="del_vertices" arity="2" since=""/> <fsummary>Delete vertices from a digraph.</fsummary> <desc> <p>Deletes the vertices in list <c><anno>Vertices</anno></c> from @@ -274,7 +274,7 @@ </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Delete a digraph.</fsummary> <desc> <p>Deletes digraph <c><anno>G</anno></c>. This call is important @@ -285,7 +285,7 @@ </func> <func> - <name name="edge" arity="2"/> + <name name="edge" arity="2" since=""/> <fsummary>Return the vertices and the label of an edge of a digraph. </fsummary> <desc> @@ -303,7 +303,7 @@ </func> <func> - <name name="edges" arity="1"/> + <name name="edges" arity="1" since=""/> <fsummary>Return all edges of a digraph.</fsummary> <desc> <p>Returns a list of all edges of digraph <c><anno>G</anno></c>, in @@ -312,7 +312,7 @@ </func> <func> - <name name="edges" arity="2"/> + <name name="edges" arity="2" since=""/> <fsummary>Return the edges emanating from or incident on a vertex of a digraph.</fsummary> <desc> @@ -324,7 +324,7 @@ </func> <func> - <name name="get_cycle" arity="2"/> + <name name="get_cycle" arity="2" since=""/> <fsummary>Find one cycle in a digraph.</fsummary> <desc> <p>If a <seealso marker="#simple_cycle">simple cycle</seealso> of @@ -341,7 +341,7 @@ </func> <func> - <name name="get_path" arity="3"/> + <name name="get_path" arity="3" since=""/> <fsummary>Find one path in a digraph.</fsummary> <desc> <p>Tries to find @@ -357,7 +357,7 @@ </func> <func> - <name name="get_short_cycle" arity="2"/> + <name name="get_short_cycle" arity="2" since=""/> <fsummary>Find one short cycle in a digraph.</fsummary> <desc> <p>Tries to find an as short as possible @@ -375,7 +375,7 @@ </func> <func> - <name name="get_short_path" arity="3"/> + <name name="get_short_path" arity="3" since=""/> <fsummary>Find one short path in a digraph.</fsummary> <desc> <p>Tries to find an as short as possible @@ -392,7 +392,7 @@ </func> <func> - <name name="in_degree" arity="2"/> + <name name="in_degree" arity="2" since=""/> <fsummary>Return the in-degree of a vertex of a digraph.</fsummary> <desc> <p>Returns the <seealso marker="#in_degree">in-degree</seealso> of @@ -401,7 +401,7 @@ </func> <func> - <name name="in_edges" arity="2"/> + <name name="in_edges" arity="2" since=""/> <fsummary>Return all edges incident on a vertex of a digraph.</fsummary> <desc> <p>Returns a list of all @@ -412,7 +412,7 @@ </func> <func> - <name name="in_neighbours" arity="2"/> + <name name="in_neighbours" arity="2" since=""/> <fsummary>Return all in-neighbors of a vertex of a digraph.</fsummary> <desc> <p>Returns a list of @@ -423,7 +423,7 @@ </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Return information about a digraph.</fsummary> <type name="d_cyclicity"/> <type name="d_protection"/> @@ -453,7 +453,7 @@ </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Return a protected empty digraph, where cycles are allowed. </fsummary> <desc> @@ -462,7 +462,7 @@ </func> <func> - <name name="new" arity="1"/> + <name name="new" arity="1" since=""/> <fsummary>Create a new empty digraph.</fsummary> <type variable="Type"/> <type name="d_type"/> @@ -492,7 +492,7 @@ </func> <func> - <name name="no_edges" arity="1"/> + <name name="no_edges" arity="1" since=""/> <fsummary>Return the number of edges of a digraph.</fsummary> <desc> <p>Returns the number of edges of digraph <c><anno>G</anno></c>.</p> @@ -500,7 +500,7 @@ </func> <func> - <name name="no_vertices" arity="1"/> + <name name="no_vertices" arity="1" since=""/> <fsummary>Return the number of vertices of a digraph.</fsummary> <desc> <p>Returns the number of vertices of digraph <c><anno>G</anno></c>.</p> @@ -508,7 +508,7 @@ </func> <func> - <name name="out_degree" arity="2"/> + <name name="out_degree" arity="2" since=""/> <fsummary>Return the out-degree of a vertex of a digraph.</fsummary> <desc> <p>Returns the <seealso marker="#out_degree">out-degree</seealso> of @@ -517,7 +517,7 @@ </func> <func> - <name name="out_edges" arity="2"/> + <name name="out_edges" arity="2" since=""/> <fsummary>Return all edges emanating from a vertex of a digraph. </fsummary> <desc> @@ -529,7 +529,7 @@ </func> <func> - <name name="out_neighbours" arity="2"/> + <name name="out_neighbours" arity="2" since=""/> <fsummary>Return all out-neighbors of a vertex of a digraph.</fsummary> <desc> <p>Returns a list of @@ -540,7 +540,7 @@ </func> <func> - <name name="vertex" arity="2"/> + <name name="vertex" arity="2" since=""/> <fsummary>Return the label of a vertex of a digraph.</fsummary> <desc> <p>Returns <c>{<anno>V</anno>, <anno>Label</anno>}</c>, @@ -553,7 +553,7 @@ </func> <func> - <name name="vertices" arity="1"/> + <name name="vertices" arity="1" since=""/> <fsummary>Return all vertices of a digraph.</fsummary> <desc> <p>Returns a list of all vertices of digraph <c><anno>G</anno></c>, in diff --git a/lib/stdlib/doc/src/digraph_utils.xml b/lib/stdlib/doc/src/digraph_utils.xml index cb316e5b93..13b0aaad9e 100644 --- a/lib/stdlib/doc/src/digraph_utils.xml +++ b/lib/stdlib/doc/src/digraph_utils.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>digraph_utils.xml</file> </header> - <module>digraph_utils</module> + <module since="">digraph_utils</module> <modulesummary>Algorithms for directed graphs.</modulesummary> <description> <p>This module provides algorithms based on depth-first traversal of @@ -154,7 +154,7 @@ <funcs> <func> - <name name="arborescence_root" arity="1"/> + <name name="arborescence_root" arity="1" since=""/> <fsummary>Check if a digraph is an arborescence.</fsummary> <desc> <p>Returns <c>{yes, <anno>Root</anno>}</c> if <c><anno>Root</anno></c> @@ -164,7 +164,7 @@ </func> <func> - <name name="components" arity="1"/> + <name name="components" arity="1" since=""/> <fsummary>Return the components of a digraph.</fsummary> <desc> <p>Returns a list @@ -177,7 +177,7 @@ </func> <func> - <name name="condensation" arity="1"/> + <name name="condensation" arity="1" since=""/> <fsummary>Return a condensed graph of a digraph.</fsummary> <desc> <p>Creates a digraph where the vertices are @@ -202,7 +202,7 @@ </func> <func> - <name name="cyclic_strong_components" arity="1"/> + <name name="cyclic_strong_components" arity="1" since=""/> <fsummary>Return the cyclic strong components of a digraph.</fsummary> <desc> <p>Returns a list of <seealso marker="#strong_components">strongly @@ -218,7 +218,7 @@ </func> <func> - <name name="is_acyclic" arity="1"/> + <name name="is_acyclic" arity="1" since=""/> <fsummary>Check if a digraph is acyclic.</fsummary> <desc> <p>Returns <c>true</c> if and only if digraph @@ -228,7 +228,7 @@ </func> <func> - <name name="is_arborescence" arity="1"/> + <name name="is_arborescence" arity="1" since=""/> <fsummary>Check if a digraph is an arborescence.</fsummary> <desc> <p>Returns <c>true</c> if and only if digraph @@ -238,7 +238,7 @@ </func> <func> - <name name="is_tree" arity="1"/> + <name name="is_tree" arity="1" since=""/> <fsummary>Check if a digraph is a tree.</fsummary> <desc> <p>Returns <c>true</c> if and only if digraph @@ -248,7 +248,7 @@ </func> <func> - <name name="loop_vertices" arity="1"/> + <name name="loop_vertices" arity="1" since=""/> <fsummary>Return the vertices of a digraph included in some loop. </fsummary> <desc> @@ -258,7 +258,7 @@ </func> <func> - <name name="postorder" arity="1"/> + <name name="postorder" arity="1" since=""/> <fsummary>Return the vertices of a digraph in postorder.</fsummary> <desc> <p>Returns all vertices of digraph <c><anno>Digraph</anno></c>. @@ -273,7 +273,7 @@ </func> <func> - <name name="preorder" arity="1"/> + <name name="preorder" arity="1" since=""/> <fsummary>Return the vertices of a digraph in preorder.</fsummary> <desc> <p>Returns all vertices of digraph <c><anno>Digraph</anno></c>. @@ -285,7 +285,7 @@ </func> <func> - <name name="reachable" arity="2"/> + <name name="reachable" arity="2" since=""/> <fsummary>Return the vertices reachable from some vertices of a digraph. </fsummary> <desc> @@ -300,7 +300,7 @@ </func> <func> - <name name="reachable_neighbours" arity="2"/> + <name name="reachable_neighbours" arity="2" since=""/> <fsummary>Return the neighbors reachable from some vertices of a digraph.</fsummary> <desc> @@ -316,7 +316,7 @@ </func> <func> - <name name="reaching" arity="2"/> + <name name="reaching" arity="2" since=""/> <fsummary>Return the vertices that reach some vertices of a digraph. </fsummary> <desc> @@ -330,7 +330,7 @@ </func> <func> - <name name="reaching_neighbours" arity="2"/> + <name name="reaching_neighbours" arity="2" since=""/> <fsummary>Return the neighbors that reach some vertices of a digraph. </fsummary> <desc> @@ -345,7 +345,7 @@ </func> <func> - <name name="strong_components" arity="1"/> + <name name="strong_components" arity="1" since=""/> <fsummary>Return the strong components of a digraph.</fsummary> <desc> <p>Returns a list of <seealso marker="#strong_components">strongly @@ -359,8 +359,8 @@ </func> <func> - <name name="subgraph" arity="2"/> - <name name="subgraph" arity="3"/> + <name name="subgraph" arity="2" since=""/> + <name name="subgraph" arity="3" since=""/> <fsummary>Return a subgraph of a digraph.</fsummary> <desc> <p>Creates a maximal <seealso marker="#subgraph">subgraph</seealso> @@ -387,7 +387,7 @@ </func> <func> - <name name="topsort" arity="1"/> + <name name="topsort" arity="1" since=""/> <fsummary>Return a topological sorting of the vertices of a digraph. </fsummary> <desc> diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml index d803d259aa..110c1cea2c 100644 --- a/lib/stdlib/doc/src/epp.xml +++ b/lib/stdlib/doc/src/epp.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>epp.xml</file> </header> - <module>epp</module> + <module since="">epp</module> <modulesummary>An Erlang code preprocessor.</modulesummary> <description> <p>The Erlang code preprocessor includes functions that are used by the @@ -76,7 +76,7 @@ <funcs> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close the preprocessing of the file associated with <c>Epp</c>. </fsummary> <desc> @@ -85,7 +85,7 @@ </func> <func> - <name name="default_encoding" arity="0"/> + <name name="default_encoding" arity="0" since="OTP R16B"/> <fsummary>Return the default encoding of Erlang source files.</fsummary> <desc> <p>Returns the default encoding of Erlang source files.</p> @@ -93,7 +93,7 @@ </func> <func> - <name name="encoding_to_string" arity="1"/> + <name name="encoding_to_string" arity="1" since="OTP R16B"/> <fsummary>Return a string representation of an encoding.</fsummary> <desc> <p>Returns a string representation of an encoding. The string @@ -107,7 +107,7 @@ </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since="OTP R14B03"/> <fsummary>Format an error descriptor.</fsummary> <desc> <p>Takes an <c><anno>ErrorDescriptor</anno></c> and returns @@ -120,7 +120,7 @@ </func> <func> - <name name="open" arity="1"/> + <name name="open" arity="1" since="OTP 17.0"/> <fsummary>Open a file for preprocessing.</fsummary> <desc> <p>Opens a file for preprocessing.</p> @@ -136,7 +136,7 @@ </func> <func> - <name name="open" arity="2"/> + <name name="open" arity="2" since=""/> <fsummary>Open a file for preprocessing.</fsummary> <desc> <p>Equivalent to @@ -145,7 +145,7 @@ </func> <func> - <name name="open" arity="3"/> + <name name="open" arity="3" since=""/> <fsummary>Open a file for preprocessing.</fsummary> <desc> <p>Equivalent to <c>epp:open([{name, FileName}, {includes, IncludePath}, @@ -154,7 +154,7 @@ </func> <func> - <name name="parse_erl_form" arity="1"/> + <name name="parse_erl_form" arity="1" since=""/> <fsummary>Return the next Erlang form from the opened Erlang source file. </fsummary> <type name="warning_info"/> @@ -167,7 +167,7 @@ </func> <func> - <name name="parse_file" arity="2"/> + <name name="parse_file" arity="2" since="OTP 17.0"/> <fsummary>Preprocess and parse an Erlang source file.</fsummary> <desc> <p>Preprocesses and parses an Erlang source file. @@ -185,7 +185,7 @@ </func> <func> - <name name="parse_file" arity="3"/> + <name name="parse_file" arity="3" since=""/> <fsummary>Preprocess and parse an Erlang source file.</fsummary> <desc> <p>Equivalent to <c>epp:parse_file(FileName, [{includes, IncludePath}, @@ -194,8 +194,8 @@ </func> <func> - <name name="read_encoding" arity="1"/> - <name name="read_encoding" arity="2"/> + <name name="read_encoding" arity="1" since="OTP R16B"/> + <name name="read_encoding" arity="2" since="OTP R16B"/> <fsummary>Read the encoding from a file.</fsummary> <desc> <p>Read the <seealso marker="#encoding">encoding</seealso> from @@ -209,8 +209,8 @@ </func> <func> - <name name="read_encoding_from_binary" arity="1"/> - <name name="read_encoding_from_binary" arity="2"/> + <name name="read_encoding_from_binary" arity="1" since="OTP R16B"/> + <name name="read_encoding_from_binary" arity="2" since="OTP R16B"/> <fsummary>Read the encoding from a binary.</fsummary> <desc> <p>Read the <seealso marker="#encoding">encoding</seealso> from @@ -224,7 +224,7 @@ </func> <func> - <name name="set_encoding" arity="1"/> + <name name="set_encoding" arity="1" since="OTP R16B"/> <fsummary>Read and set the encoding of an I/O device.</fsummary> <desc> <p>Reads the <seealso marker="#encoding">encoding</seealso> from @@ -239,7 +239,7 @@ </func> <func> - <name name="set_encoding" arity="2"/> + <name name="set_encoding" arity="2" since="OTP 17.0"/> <fsummary>Read and set the encoding of an I/O device.</fsummary> <desc> <p>Reads the <seealso marker="#encoding">encoding</seealso> from diff --git a/lib/stdlib/doc/src/erl_anno.xml b/lib/stdlib/doc/src/erl_anno.xml index f316f63d98..dff93619ab 100644 --- a/lib/stdlib/doc/src/erl_anno.xml +++ b/lib/stdlib/doc/src/erl_anno.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>erl_anno.xml</file> </header> - <module>erl_anno</module> + <module since="OTP 18.0">erl_anno</module> <modulesummary>Abstract datatype for the annotations of the Erlang Compiler. </modulesummary> @@ -135,7 +135,7 @@ <funcs> <func> - <name name="column" arity="1"/> + <name name="column" arity="1" since="OTP 18.0"/> <fsummary>Return the column.</fsummary> <type name="column"></type> <desc> @@ -144,7 +144,7 @@ </func> <func> - <name name="end_location" arity="1"/> + <name name="end_location" arity="1" since="OTP 18.0"/> <fsummary>Return the end location of the text.</fsummary> <type name="location"></type> <desc> @@ -155,7 +155,7 @@ </func> <func> - <name name="file" arity="1"/> + <name name="file" arity="1" since="OTP 18.0"/> <fsummary>Return the filename.</fsummary> <type name="filename"></type> <desc> @@ -165,7 +165,7 @@ </func> <func> - <name name="from_term" arity="1"/> + <name name="from_term" arity="1" since="OTP 18.0"/> <fsummary>Return annotations given a term.</fsummary> <desc> <p>Returns annotations with representation <anno>Term</anno>.</p> @@ -174,7 +174,7 @@ </func> <func> - <name name="generated" arity="1"/> + <name name="generated" arity="1" since="OTP 18.0"/> <fsummary>Return the generated Boolean.</fsummary> <type name="generated"></type> <desc> @@ -185,7 +185,7 @@ </func> <func> - <name name="is_anno" arity="1"/> + <name name="is_anno" arity="1" since="OTP 18.0"/> <fsummary>Test for a collection of annotations.</fsummary> <desc> <p>Returns <c>true</c> if <anno>Term</anno> is a collection of @@ -194,7 +194,7 @@ </func> <func> - <name name="line" arity="1"/> + <name name="line" arity="1" since="OTP 18.0"/> <fsummary>Return the line.</fsummary> <type name="line"></type> <desc> @@ -203,7 +203,7 @@ </func> <func> - <name name="location" arity="1"/> + <name name="location" arity="1" since="OTP 18.0"/> <fsummary>Return the location.</fsummary> <type name="location"></type> <desc> @@ -212,7 +212,7 @@ </func> <func> - <name name="new" arity="1"/> + <name name="new" arity="1" since="OTP 18.0"/> <fsummary>Create a new collection of annotations.</fsummary> <type name="location"></type> <desc> @@ -221,7 +221,7 @@ </func> <func> - <name name="set_file" arity="2"/> + <name name="set_file" arity="2" since="OTP 18.0"/> <fsummary>Modify the filename.</fsummary> <type name="filename"></type> <desc> @@ -230,7 +230,7 @@ </func> <func> - <name name="set_generated" arity="2"/> + <name name="set_generated" arity="2" since="OTP 18.0"/> <fsummary>Modify the generated marker.</fsummary> <type name="generated"></type> <desc> @@ -240,7 +240,7 @@ </func> <func> - <name name="set_line" arity="2"/> + <name name="set_line" arity="2" since="OTP 18.0"/> <fsummary>Modify the line.</fsummary> <type name="line"></type> <desc> @@ -249,7 +249,7 @@ </func> <func> - <name name="set_location" arity="2"/> + <name name="set_location" arity="2" since="OTP 18.0"/> <fsummary>Modify the location.</fsummary> <type name="location"></type> <desc> @@ -258,7 +258,7 @@ </func> <func> - <name name="set_record" arity="2"/> + <name name="set_record" arity="2" since="OTP 18.0"/> <fsummary>Modify the record marker.</fsummary> <type name="record"></type> <desc> @@ -267,7 +267,7 @@ </func> <func> - <name name="set_text" arity="2"/> + <name name="set_text" arity="2" since="OTP 18.0"/> <fsummary>Modify the text.</fsummary> <type name="text"></type> <desc> @@ -276,7 +276,7 @@ </func> <func> - <name name="text" arity="1"/> + <name name="text" arity="1" since="OTP 18.0"/> <fsummary>Return the text.</fsummary> <type name="text"></type> <desc> @@ -286,7 +286,7 @@ </func> <func> - <name name="to_term" arity="1"/> + <name name="to_term" arity="1" since="OTP 18.0"/> <fsummary>Return the term representing a collection of annotations. </fsummary> <desc> diff --git a/lib/stdlib/doc/src/erl_eval.xml b/lib/stdlib/doc/src/erl_eval.xml index 1c0f7f062f..813cbecd89 100644 --- a/lib/stdlib/doc/src/erl_eval.xml +++ b/lib/stdlib/doc/src/erl_eval.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>erl_eval.xml</file> </header> - <module>erl_eval</module> + <module since="">erl_eval</module> <modulesummary>The Erlang meta interpreter.</modulesummary> <description> <p>This module provides an interpreter for Erlang expressions. The @@ -96,7 +96,7 @@ <funcs> <func> - <name name="add_binding" arity="3"/> + <name name="add_binding" arity="3" since=""/> <fsummary>Add a binding.</fsummary> <desc> <p>Adds binding <c><anno>Name</anno>=<anno>Value</anno></c> @@ -106,7 +106,7 @@ </func> <func> - <name name="binding" arity="2"/> + <name name="binding" arity="2" since=""/> <fsummary>Return bindings.</fsummary> <desc> <p>Returns the binding of <c><anno>Name</anno></c> @@ -115,7 +115,7 @@ </func> <func> - <name name="bindings" arity="1"/> + <name name="bindings" arity="1" since=""/> <fsummary>Return bindings.</fsummary> <desc> <p>Returns the list of bindings contained in the binding @@ -124,7 +124,7 @@ </func> <func> - <name name="del_binding" arity="2"/> + <name name="del_binding" arity="2" since=""/> <fsummary>Delete a binding.</fsummary> <desc> <p>Removes the binding of <c><anno>Name</anno></c> @@ -134,10 +134,10 @@ </func> <func> - <name name="expr" arity="2"/> - <name name="expr" arity="3"/> - <name name="expr" arity="4"/> - <name name="expr" arity="5"/> + <name name="expr" arity="2" since=""/> + <name name="expr" arity="3" since=""/> + <name name="expr" arity="4" since=""/> + <name name="expr" arity="5" since=""/> <fsummary>Evaluate expression.</fsummary> <desc> <p>Evaluates <c><anno>Expression</anno></c> with the set of bindings @@ -157,9 +157,9 @@ </func> <func> - <name name="expr_list" arity="2"/> - <name name="expr_list" arity="3"/> - <name name="expr_list" arity="4"/> + <name name="expr_list" arity="2" since=""/> + <name name="expr_list" arity="3" since=""/> + <name name="expr_list" arity="4" since=""/> <fsummary>Evaluate a list of expressions.</fsummary> <desc> <p>Evaluates a list of expressions in parallel, using the same @@ -174,9 +174,9 @@ </func> <func> - <name name="exprs" arity="2"/> - <name name="exprs" arity="3"/> - <name name="exprs" arity="4"/> + <name name="exprs" arity="2" since=""/> + <name name="exprs" arity="3" since=""/> + <name name="exprs" arity="4" since=""/> <fsummary>Evaluate expressions.</fsummary> <desc> <p>Evaluates <c><anno>Expressions</anno></c> with the set of bindings @@ -197,7 +197,7 @@ </func> <func> - <name name="new_bindings" arity="0"/> + <name name="new_bindings" arity="0" since=""/> <fsummary>Return a bindings structure.</fsummary> <desc> <p>Returns an empty binding structure.</p> diff --git a/lib/stdlib/doc/src/erl_expand_records.xml b/lib/stdlib/doc/src/erl_expand_records.xml index b6aa75ed03..20e5f1960b 100644 --- a/lib/stdlib/doc/src/erl_expand_records.xml +++ b/lib/stdlib/doc/src/erl_expand_records.xml @@ -34,7 +34,7 @@ <rev>PA1</rev> <file>erl_expand_records.xml</file> </header> - <module>erl_expand_records</module> + <module since="">erl_expand_records</module> <modulesummary>Expands records in a module.</modulesummary> <description> <p>This module expands records in a module.</p> @@ -42,7 +42,7 @@ <funcs> <func> - <name name="module" arity="2"/> + <name name="module" arity="2" since=""/> <fsummary>Expand all records in a module.</fsummary> <desc> <p>Expands all records in a module to use explicit tuple diff --git a/lib/stdlib/doc/src/erl_id_trans.xml b/lib/stdlib/doc/src/erl_id_trans.xml index 16952a9582..ec66842ac0 100644 --- a/lib/stdlib/doc/src/erl_id_trans.xml +++ b/lib/stdlib/doc/src/erl_id_trans.xml @@ -34,7 +34,7 @@ <rev>B</rev> <file>erl_id_trans.xml</file> </header> - <module>erl_id_trans</module> + <module since="">erl_id_trans</module> <modulesummary>An identity parse transform.</modulesummary> <description> <p>This module performs an identity parse transformation of Erlang code. @@ -46,7 +46,7 @@ <funcs> <func> - <name>parse_transform(Forms, Options) -> Forms</name> + <name since="">parse_transform(Forms, Options) -> Forms</name> <fsummary>Transform Erlang forms.</fsummary> <type> <v>Forms = [<seealso marker="erl_parse#type-abstract_form">erl_parse:abstract_form()</seealso> diff --git a/lib/stdlib/doc/src/erl_internal.xml b/lib/stdlib/doc/src/erl_internal.xml index 17cd0fb240..77551ffed7 100644 --- a/lib/stdlib/doc/src/erl_internal.xml +++ b/lib/stdlib/doc/src/erl_internal.xml @@ -34,7 +34,7 @@ <rev>B</rev> <file>erl_internal.xml</file> </header> - <module>erl_internal</module> + <module since="">erl_internal</module> <modulesummary>Internal Erlang definitions.</modulesummary> <description> <p>This module defines Erlang BIFs, guard tests, and operators. @@ -44,7 +44,7 @@ <funcs> <func> - <name name="add_predefined_functions" arity="1"/> + <name name="add_predefined_functions" arity="1" since="OTP 20.0"/> <fsummary>Add code for pre-defined functions.</fsummary> <desc> <p>Adds to <c><anno>Forms</anno></c> the code for the standard @@ -54,7 +54,7 @@ </func> <func> - <name name="arith_op" arity="2"/> + <name name="arith_op" arity="2" since=""/> <fsummary>Test for an arithmetic operator.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c> @@ -63,7 +63,7 @@ </func> <func> - <name name="bif" arity="2"/> + <name name="bif" arity="2" since=""/> <fsummary>Test for an Erlang BIF.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> @@ -73,7 +73,7 @@ </func> <func> - <name name="bool_op" arity="2"/> + <name name="bool_op" arity="2" since=""/> <fsummary>Test for a Boolean operator.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c> @@ -82,7 +82,7 @@ </func> <func> - <name name="comp_op" arity="2"/> + <name name="comp_op" arity="2" since=""/> <fsummary>Test for a comparison operator.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c> @@ -91,7 +91,7 @@ </func> <func> - <name name="guard_bif" arity="2"/> + <name name="guard_bif" arity="2" since=""/> <fsummary>Test for an Erlang BIF allowed in guards.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> is @@ -100,7 +100,7 @@ </func> <func> - <name name="list_op" arity="2"/> + <name name="list_op" arity="2" since=""/> <fsummary>Test for a list operator.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c> @@ -109,7 +109,7 @@ </func> <func> - <name name="op_type" arity="2"/> + <name name="op_type" arity="2" since=""/> <fsummary>Return operator type.</fsummary> <desc> <p>Returns the <c><anno>Type</anno></c> of operator that @@ -120,7 +120,7 @@ </func> <func> - <name name="send_op" arity="2"/> + <name name="send_op" arity="2" since=""/> <fsummary>Test for a send operator.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>OpName</anno>/<anno>Arity</anno></c> @@ -129,7 +129,7 @@ </func> <func> - <name name="type_test" arity="2"/> + <name name="type_test" arity="2" since=""/> <fsummary>Test for a valid type test.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Name</anno>/<anno>Arity</anno></c> is diff --git a/lib/stdlib/doc/src/erl_lint.xml b/lib/stdlib/doc/src/erl_lint.xml index 77cb7a9916..12eaafc3a8 100644 --- a/lib/stdlib/doc/src/erl_lint.xml +++ b/lib/stdlib/doc/src/erl_lint.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>erl_lint.xml</file> </header> - <module>erl_lint</module> + <module since="">erl_lint</module> <modulesummary>The Erlang code linter.</modulesummary> <description> <p>This module is used to check Erlang code for illegal syntax and @@ -78,7 +78,7 @@ <funcs> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Format an error descriptor.</fsummary> <desc> <p>Takes an <c><anno>ErrorDescriptor</anno></c> and returns a string @@ -90,7 +90,7 @@ </func> <func> - <name name="is_guard_test" arity="1"/> + <name name="is_guard_test" arity="1" since=""/> <fsummary>Test for a guard test.</fsummary> <desc> <p>Tests if <c><anno>Expr</anno></c> is a legal guard test. @@ -102,9 +102,9 @@ </func> <func> - <name name="module" arity="1"/> - <name name="module" arity="2"/> - <name name="module" arity="3"/> + <name name="module" arity="1" since=""/> + <name name="module" arity="2" since=""/> + <name name="module" arity="3" since=""/> <fsummary>Check a module for errors.</fsummary> <desc> <p>Checks all the forms in a module for errors. It returns:</p> diff --git a/lib/stdlib/doc/src/erl_parse.xml b/lib/stdlib/doc/src/erl_parse.xml index 647f36883c..8142e5c0aa 100644 --- a/lib/stdlib/doc/src/erl_parse.xml +++ b/lib/stdlib/doc/src/erl_parse.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>erl_parse.xml</file> </header> - <module>erl_parse</module> + <module since="">erl_parse</module> <modulesummary>The Erlang parser.</modulesummary> <description> <p>This module is the basic Erlang parser that converts tokens into @@ -89,7 +89,7 @@ <funcs> <func> - <name name="abstract" arity="1"/> + <name name="abstract" arity="1" since=""/> <fsummary>Convert an Erlang term into an abstract form.</fsummary> <desc> <p>Converts the Erlang data structure <c><anno>Data</anno></c> into an @@ -102,7 +102,7 @@ </func> <func> - <name name="abstract" arity="2"/> + <name name="abstract" arity="2" since="OTP R16B01"/> <fsummary>Convert an Erlang term into an abstract form.</fsummary> <type name="encoding_func"/> <desc> @@ -124,7 +124,7 @@ </func> <func> - <name name="anno_from_term" arity="1"/> + <name name="anno_from_term" arity="1" since="OTP 18.0"/> <fsummary>Return annotations as terms.</fsummary> <desc> <p>Assumes that <c><anno>Term</anno></c> is a term with the same @@ -140,7 +140,7 @@ </func> <func> - <name name="anno_to_term" arity="1"/> + <name name="anno_to_term" arity="1" since="OTP 18.0"/> <fsummary>Return the representation of annotations.</fsummary> <desc> <p>Returns a term where each collection of annotations @@ -154,7 +154,7 @@ </func> <func> - <name name="fold_anno" arity="3"/> + <name name="fold_anno" arity="3" since="OTP 18.0"/> <fsummary>Fold a function over the annotations of an <c>erl_parse</c> tree. </fsummary> <desc> @@ -171,7 +171,7 @@ </func> <func> - <name>format_error(ErrorDescriptor) -> Chars</name> + <name since="">format_error(ErrorDescriptor) -> Chars</name> <fsummary>Format an error descriptor.</fsummary> <type> <v>ErrorDescriptor = <seealso @@ -188,7 +188,7 @@ </func> <func> - <name name="map_anno" arity="2"/> + <name name="map_anno" arity="2" since="OTP 18.0"/> <fsummary>Map a function over the annotations of an <c>erl_parse</c> tree. </fsummary> <desc> @@ -201,7 +201,7 @@ </func> <func> - <name name="mapfold_anno" arity="3"/> + <name name="mapfold_anno" arity="3" since="OTP 18.0"/> <fsummary>Map and fold a function over the annotations of an <c>erl_parse</c> tree.</fsummary> <desc> @@ -220,7 +220,7 @@ </func> <func> - <name name="new_anno" arity="1"/> + <name name="new_anno" arity="1" since="OTP 18.0"/> <fsummary>Create new annotations.</fsummary> <desc> <p>Assumes that <c><anno>Term</anno></c> is a term with the same @@ -236,7 +236,7 @@ </func> <func> - <name name="normalise" arity="1"/> + <name name="normalise" arity="1" since=""/> <fsummary>Convert abstract form to an Erlang term.</fsummary> <desc> <p>Converts the abstract form <c><anno>AbsTerm</anno></c> of a @@ -247,7 +247,7 @@ </func> <func> - <name name="parse_exprs" arity="1"/> + <name name="parse_exprs" arity="1" since=""/> <fsummary>Parse Erlang expressions.</fsummary> <desc> <p>Parses <c><anno>Tokens</anno></c> as if it was a list of expressions. @@ -267,7 +267,7 @@ </func> <func> - <name name="parse_form" arity="1"/> + <name name="parse_form" arity="1" since=""/> <fsummary>Parse an Erlang form.</fsummary> <desc> <p>Parses <c><anno>Tokens</anno></c> as if it was a form. Returns one @@ -287,7 +287,7 @@ </func> <func> - <name name="parse_term" arity="1"/> + <name name="parse_term" arity="1" since=""/> <fsummary>Parse an Erlang term.</fsummary> <desc> <p>Parses <c><anno>Tokens</anno></c> as if it was a term. Returns @@ -307,8 +307,8 @@ </func> <func> - <name name="tokens" arity="1"/> - <name name="tokens" arity="2"/> + <name name="tokens" arity="1" since=""/> + <name name="tokens" arity="2" since=""/> <fsummary>Generate a list of tokens for an expression.</fsummary> <desc> <p>Generates a list of tokens representing the abstract diff --git a/lib/stdlib/doc/src/erl_pp.xml b/lib/stdlib/doc/src/erl_pp.xml index 77a7f1e8d1..f1c3aa5a41 100644 --- a/lib/stdlib/doc/src/erl_pp.xml +++ b/lib/stdlib/doc/src/erl_pp.xml @@ -34,7 +34,7 @@ <rev>B</rev> <file>erl_pp.xml</file> </header> - <module>erl_pp</module> + <module since="">erl_pp</module> <modulesummary>The Erlang pretty printer.</modulesummary> <description> <p>The functions in this module are used to generate @@ -73,8 +73,8 @@ <funcs> <func> - <name name="attribute" arity="1"/> - <name name="attribute" arity="2"/> + <name name="attribute" arity="1" since=""/> + <name name="attribute" arity="2" since=""/> <fsummary>Pretty print an attribute.</fsummary> <desc> <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>, @@ -83,10 +83,10 @@ </func> <func> - <name name="expr" arity="1"/> - <name name="expr" arity="2"/> - <name name="expr" arity="3"/> - <name name="expr" arity="4"/> + <name name="expr" arity="1" since=""/> + <name name="expr" arity="2" since=""/> + <name name="expr" arity="3" since=""/> + <name name="expr" arity="4" since=""/> <fsummary>Pretty print one <c>Expression</c>.</fsummary> <desc> <p>Prints one expression. It is useful for implementing hooks (see @@ -96,9 +96,9 @@ </func> <func> - <name name="exprs" arity="1"/> - <name name="exprs" arity="2"/> - <name name="exprs" arity="3"/> + <name name="exprs" arity="1" since=""/> + <name name="exprs" arity="2" since=""/> + <name name="exprs" arity="3" since=""/> <fsummary>Pretty print <c>Expressions</c>.</fsummary> <desc> <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>, @@ -108,8 +108,8 @@ </func> <func> - <name name="form" arity="1"/> - <name name="form" arity="2"/> + <name name="form" arity="1" since=""/> + <name name="form" arity="2" since=""/> <fsummary>Pretty print a form.</fsummary> <desc> <p>Pretty prints a @@ -120,8 +120,8 @@ </func> <func> - <name name="function" arity="1"/> - <name name="function" arity="2"/> + <name name="function" arity="1" since=""/> + <name name="function" arity="2" since=""/> <fsummary>Pretty print a function.</fsummary> <desc> <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>, @@ -130,8 +130,8 @@ </func> <func> - <name name="guard" arity="1"/> - <name name="guard" arity="2"/> + <name name="guard" arity="1" since=""/> + <name name="guard" arity="2" since=""/> <fsummary>Pretty print a guard.</fsummary> <desc> <p>Same as <seealso marker="#form/1"><c>form/1,2</c></seealso>, diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml index 137ccd3416..38111f73bc 100644 --- a/lib/stdlib/doc/src/erl_scan.xml +++ b/lib/stdlib/doc/src/erl_scan.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>erl_scan.xml</file> </header> - <module>erl_scan</module> + <module since="">erl_scan</module> <modulesummary>The Erlang token scanner.</modulesummary> <description> <p>This module contains functions for tokenizing (scanning) characters into @@ -74,7 +74,7 @@ <funcs> <func> - <name name="category" arity="1"/> + <name name="category" arity="1" since="OTP 18.0"/> <fsummary>Return the category.</fsummary> <desc> <p>Returns the category of <c><anno>Token</anno></c>.</p> @@ -82,7 +82,7 @@ </func> <func> - <name name="column" arity="1"/> + <name name="column" arity="1" since="OTP 18.0"/> <fsummary>Return the column.</fsummary> <desc> <p>Returns the column of <c><anno>Token</anno></c>'s @@ -91,7 +91,7 @@ </func> <func> - <name name="end_location" arity="1"/> + <name name="end_location" arity="1" since="OTP 18.0"/> <fsummary>Return the end location of the text.</fsummary> <desc> <p>Returns the end location of the text of @@ -101,7 +101,7 @@ </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Format an error descriptor.</fsummary> <desc> <p>Uses an <c><anno>ErrorDescriptor</anno></c> and returns a string @@ -113,7 +113,7 @@ </func> <func> - <name name="line" arity="1"/> + <name name="line" arity="1" since="OTP 18.0"/> <fsummary>Return the line.</fsummary> <desc> <p>Returns the line of <c><anno>Token</anno></c>'s collection @@ -122,7 +122,7 @@ </func> <func> - <name name="location" arity="1"/> + <name name="location" arity="1" since="OTP 18.0"/> <fsummary>Return the location.</fsummary> <desc> <p>Returns the location of <c><anno>Token</anno></c>'s @@ -131,7 +131,7 @@ </func> <func> - <name name="reserved_word" arity="1"/> + <name name="reserved_word" arity="1" since=""/> <fsummary>Test for a reserved word.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Atom</anno></c> is an @@ -140,9 +140,9 @@ </func> <func> - <name name="string" arity="1"/> - <name name="string" arity="2"/> - <name name="string" arity="3"/> + <name name="string" arity="1" since=""/> + <name name="string" arity="2" since=""/> + <name name="string" arity="3" since=""/> <fsummary>Scan a string and return the Erlang tokens.</fsummary> <desc> <p>Takes the list of characters <c><anno>String</anno></c> and tries to @@ -229,7 +229,7 @@ </func> <func> - <name name="symbol" arity="1"/> + <name name="symbol" arity="1" since="OTP 18.0"/> <fsummary>Return the symbol.</fsummary> <desc> <p>Returns the symbol of <c><anno>Token</anno></c>.</p> @@ -237,7 +237,7 @@ </func> <func> - <name name="text" arity="1"/> + <name name="text" arity="1" since="OTP 18.0"/> <fsummary>Return the text.</fsummary> <desc> <p>Returns the text of <c><anno>Token</anno></c>'s collection @@ -247,8 +247,8 @@ </func> <func> - <name name="tokens" arity="3"/> - <name name="tokens" arity="4"/> + <name name="tokens" arity="3" since=""/> + <name name="tokens" arity="4" since=""/> <fsummary>Re-entrant scanner.</fsummary> <type name="char_spec"/> <type name="return_cont"/> diff --git a/lib/stdlib/doc/src/erl_tar.xml b/lib/stdlib/doc/src/erl_tar.xml index 68fa071090..ea8173748a 100644 --- a/lib/stdlib/doc/src/erl_tar.xml +++ b/lib/stdlib/doc/src/erl_tar.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>erl_tar.xml</file> </header> - <module>erl_tar</module> + <module since="">erl_tar</module> <modulesummary>Unix 'tar' utility for reading and writing tar archives. </modulesummary> <description> @@ -127,7 +127,7 @@ <funcs> <func> - <name>add(TarDescriptor, Filename, Options) -> RetValue</name> + <name since="">add(TarDescriptor, Filename, Options) -> RetValue</name> <fsummary>Add a file to an open tar file.</fsummary> <type> <v>TarDescriptor = term()</v> @@ -211,7 +211,7 @@ </func> <func> - <name>add(TarDescriptor, FilenameOrBin, NameInArchive, Options) -> + <name since="">add(TarDescriptor, FilenameOrBin, NameInArchive, Options) -> RetValue </name> <fsummary>Add a file to an open tar file.</fsummary> <type> @@ -233,7 +233,7 @@ </func> <func> - <name>close(TarDescriptor)</name> + <name since="">close(TarDescriptor)</name> <fsummary>Close an open tar file.</fsummary> <type> <v>TarDescriptor = term()</v> @@ -245,7 +245,7 @@ </func> <func> - <name>create(Name, FileList) ->RetValue </name> + <name since="">create(Name, FileList) ->RetValue </name> <fsummary>Create a tar archive.</fsummary> <type> <v>Name = filename()</v> @@ -264,7 +264,7 @@ </func> <func> - <name>create(Name, FileList, OptionList)</name> + <name since="">create(Name, FileList, OptionList)</name> <fsummary>Create a tar archive with options.</fsummary> <type> <v>Name = filename()</v> @@ -315,7 +315,7 @@ </func> <func> - <name>extract(Name) -> RetValue</name> + <name since="">extract(Name) -> RetValue</name> <fsummary>Extract all files from a tar file.</fsummary> <type> <v>Name = filename() | {binary,binary()} | {file,Fd}</v> @@ -339,7 +339,7 @@ </func> <func> - <name>extract(Name, OptionList)</name> + <name since="">extract(Name, OptionList)</name> <fsummary>Extract files from a tar file.</fsummary> <type> <v>Name = filename() | {binary,binary()} | {file,Fd}</v> @@ -411,7 +411,7 @@ </func> <func> - <name>format_error(Reason) -> string()</name> + <name since="">format_error(Reason) -> string()</name> <fsummary>Convert error term to a readable string.</fsummary> <type> <v>Reason = term()</v> @@ -423,7 +423,7 @@ </func> <func> - <name>init(UserPrivate, AccessMode, Fun) -> + <name since="OTP 17.4">init(UserPrivate, AccessMode, Fun) -> {ok,TarDescriptor} | {error,Reason}</name> <fsummary>Create a <c>TarDescriptor</c> used in subsequent tar operations when defining own low-level storage access functions.</fsummary> @@ -518,7 +518,7 @@ erl_tar:close(TarDesc)</code> </func> <func> - <name>open(Name, OpenModeList) -> RetValue</name> + <name since="">open(Name, OpenModeList) -> RetValue</name> <fsummary>Open a tar file for writing.</fsummary> <type> <v>Name = filename()</v> @@ -565,7 +565,7 @@ erl_tar:close(TarDesc)</code> </func> <func> - <name>table(Name) -> RetValue</name> + <name since="">table(Name) -> RetValue</name> <fsummary>Retrieve the name of all files in a tar file.</fsummary> <type> <v>Name = filename()|{binary,binary()}|{file,file_descriptor()}</v> @@ -578,7 +578,7 @@ erl_tar:close(TarDesc)</code> </func> <func> - <name>table(Name, Options)</name> + <name since="">table(Name, Options)</name> <fsummary>Retrieve name and information of all files in a tar file. </fsummary> <type> @@ -590,7 +590,7 @@ erl_tar:close(TarDesc)</code> </func> <func> - <name>t(Name)</name> + <name since="">t(Name)</name> <fsummary>Print the name of each file in a tar file.</fsummary> <type> <v>Name = filename()|{binary,binary()}|{file,file_descriptor()}</v> @@ -602,7 +602,7 @@ erl_tar:close(TarDesc)</code> </func> <func> - <name>tt(Name)</name> + <name since="">tt(Name)</name> <fsummary>Print name and information for each file in a tar file. </fsummary> <type> diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml index 611b176613..e67397c6fd 100644 --- a/lib/stdlib/doc/src/ets.xml +++ b/lib/stdlib/doc/src/ets.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>ets</module> + <module since="">ets</module> <modulesummary>Built-in term storage.</modulesummary> <description> <p>This module is an interface to the Erlang built-in term storage @@ -138,23 +138,56 @@ operation. In database terms the isolation level can be seen as "serializable", as if all isolated operations are carried out serially, one after the other in a strict order.</p> + </section> - <p>No other support is available within this module that would guarantee - consistency between objects. However, function - <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso> - can be used to guarantee that a sequence of - <seealso marker="#first/1"><c>first/1</c></seealso> and - <seealso marker="#next/2"><c>next/2</c></seealso> calls traverse the - table without errors and that each existing object in the table is - visited exactly once, even if another (or the same) process - simultaneously deletes or inserts objects into the table. - Nothing else is guaranteed; in particular objects that are inserted - or deleted during such a traversal can be visited once or not at all. - Functions that internally traverse over a table, like - <seealso marker="#select/1"><c>select</c></seealso> and - <seealso marker="#match/1"><c>match</c></seealso>, - give the same guarantee as - <seealso marker="#safe_fixtable/2"><c>safe_fixtable</c></seealso>.</p> + <section><marker id="traversal"></marker> + <title>Table traversal</title> + <p>There are different ways to traverse through the objects of a table.</p> + <list type="bulleted"> + <item><p><em>Single-step</em> traversal one key at at time, using + <seealso marker="#first/1"><c>first/1</c></seealso>, + <seealso marker="#next/2"><c>next/2</c></seealso>, + <seealso marker="#last/1"><c>last/1</c></seealso> and + <seealso marker="#prev/2"><c>prev/2</c></seealso>.</p> + </item> + <item><p>Search with simple <em>match patterns</em>, using + <seealso marker="#match/1"><c>match/1/2/3</c></seealso>, + <seealso marker="#match_delete/2"><c>match_delete/2</c></seealso> and + <seealso marker="#match_object/1"><c>match_object/1/2/3</c></seealso>.</p> + </item> + <item><p>Search with more powerful <em>match specifications</em>, using + <seealso marker="#select/1"><c>select/1/2/3</c></seealso>, + <seealso marker="#select_count/2"><c>select_count/2</c></seealso>, + <seealso marker="#select_delete/2"><c>select_delete/2</c></seealso>, + <seealso marker="#select_replace/2"><c>select_replace/2</c></seealso> and + <seealso marker="#select_reverse/1"><c>select_reverse/1/2/3</c></seealso>.</p> + </item> + <item><p><em>Table conversions</em>, using + <seealso marker="#tab2file/2"><c>tab2file/2/3</c></seealso> and + <seealso marker="#tab2list/1"><c>tab2list/1</c></seealso>.</p> + </item> + </list> + <p>None of these ways of table traversal will guarantee a consistent table snapshot + if the table is also updated during the traversal. Moreover, traversals not + done in a <em>safe</em> way, on tables where keys are inserted or deleted + during the traversal, may yield the following undesired effects:</p> + <list type="bulleted"> + <item><p>Any key may be missed.</p></item> + <item><p>Any key may be found more than once.</p></item> + <item><p>The traversal may fail with <c>badarg</c> exception if keys are deleted.</p> + </item> + </list> + <p>A table traversal is <em>safe</em> if either</p> + <list type="bulleted"> + <item><p>the table is of type <c>ordered_set</c>.</p> + </item> + <item><p>the entire table traversal is done within one ETS function + call.</p> + </item> + <item><p>function <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso> + is used to keep the table fixated during the entire traversal.</p> + </item> + </list> </section> <section> @@ -207,7 +240,7 @@ <funcs> <func> - <name name="all" arity="0"/> + <name name="all" arity="0" since=""/> <fsummary>Return a list of all ETS tables.</fsummary> <desc> <p>Returns a list of all tables at the node. Named tables are @@ -222,7 +255,7 @@ </func> <func> - <name name="delete" arity="1"/> + <name name="delete" arity="1" since=""/> <fsummary>Delete an entire ETS table.</fsummary> <desc> <p>Deletes the entire table <c><anno>Tab</anno></c>.</p> @@ -230,7 +263,7 @@ </func> <func> - <name name="delete" arity="2"/> + <name name="delete" arity="2" since=""/> <fsummary>Delete all objects with a specified key from an ETS table.</fsummary> <desc> @@ -240,7 +273,7 @@ </func> <func> - <name name="delete_all_objects" arity="1"/> + <name name="delete_all_objects" arity="1" since=""/> <fsummary>Delete all objects in an ETS table.</fsummary> <desc> <p>Delete all objects in the ETS table <c><anno>Tab</anno></c>. @@ -250,7 +283,7 @@ </func> <func> - <name name="delete_object" arity="2"/> + <name name="delete_object" arity="2" since=""/> <fsummary>Deletes a specific from an ETS table.</fsummary> <desc> <p>Delete the exact object <c><anno>Object</anno></c> from the @@ -262,7 +295,7 @@ </func> <func> - <name name="file2tab" arity="1"/> + <name name="file2tab" arity="1" since=""/> <fsummary>Read an ETS table from a file.</fsummary> <desc> <p>Reads a file produced by <seealso marker="#tab2file/2"> @@ -274,7 +307,7 @@ </func> <func> - <name name="file2tab" arity="2"/> + <name name="file2tab" arity="2" since=""/> <fsummary>Read an ETS table from a file.</fsummary> <desc> <p>Reads a file produced by <seealso marker="#tab2file/2"> @@ -306,7 +339,7 @@ </func> <func> - <name name="first" arity="1"/> + <name name="first" arity="1" since=""/> <fsummary>Return the first key in an ETS table.</fsummary> <desc> <p>Returns the first key <c><anno>Key</anno></c> in table @@ -321,7 +354,7 @@ </func> <func> - <name name="foldl" arity="3"/> + <name name="foldl" arity="3" since=""/> <fsummary>Fold a function over an ETS table.</fsummary> <desc> <p><c><anno>Acc0</anno></c> is returned if the table is empty. @@ -337,7 +370,7 @@ </func> <func> - <name name="foldr" arity="3"/> + <name name="foldr" arity="3" since=""/> <fsummary>Fold a function over an ETS table.</fsummary> <desc> <p><c><anno>Acc0</anno></c> is returned if the table is empty. @@ -353,7 +386,7 @@ </func> <func> - <name name="from_dets" arity="2"/> + <name name="from_dets" arity="2" since=""/> <fsummary>Fill an ETS table with objects from a Dets table.</fsummary> <desc> @@ -367,7 +400,7 @@ </func> <func> - <name name="fun2ms" arity="1"/> + <name name="fun2ms" arity="1" since=""/> <fsummary>Pseudo function that transforms fun syntax to a match specification.</fsummary> <desc> @@ -436,7 +469,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="give_away" arity="3"/> + <name name="give_away" arity="3" since=""/> <fsummary>Change owner of a table.</fsummary> <desc> <p>Make process <c><anno>Pid</anno></c> the new owner of table @@ -454,7 +487,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="i" arity="0"/> + <name name="i" arity="0" since=""/> <fsummary>Display information about all ETS tables on a terminal. </fsummary> <desc> @@ -463,7 +496,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="i" arity="1"/> + <name name="i" arity="1" since=""/> <fsummary>Browse an ETS table on a terminal.</fsummary> <desc> <p>Browses table <c><anno>Tab</anno></c> on a terminal.</p> @@ -471,7 +504,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="info" arity="1"/> + <name name="info" arity="1" since=""/> <fsummary>Return information about an <c>table</c>.</fsummary> <desc> <p>Returns information about table <c><anno>Tab</anno></c> as a list of @@ -547,7 +580,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="info" arity="2"/> + <name name="info" arity="2" since=""/> <fsummary>Return the information associated with the specified item for an ETS table.</fsummary> <desc> @@ -618,7 +651,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="init_table" arity="2"/> + <name name="init_table" arity="2" since=""/> <fsummary>Replace all objects of an ETS table.</fsummary> <desc> <p>Replaces the existing objects of table <c><anno>Tab</anno></c> with @@ -648,7 +681,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="insert" arity="2"/> + <name name="insert" arity="2" since=""/> <fsummary>Insert an object into an ETS table.</fsummary> <desc> <p>Inserts the object or all of the objects in list @@ -680,7 +713,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="insert_new" arity="2"/> + <name name="insert_new" arity="2" since=""/> <fsummary>Insert an object into an ETS table if the key is not already present.</fsummary> <desc> @@ -699,7 +732,7 @@ Error: fun containing local Erlang function calls </func> <func> - <name name="is_compiled_ms" arity="1"/> + <name name="is_compiled_ms" arity="1" since=""/> <fsummary>Check if an Erlang term is the result of <c>match_spec_compile</c>.</fsummary> <desc> @@ -731,7 +764,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="last" arity="1"/> + <name name="last" arity="1" since=""/> <fsummary>Return the last key in an ETS table of type <c>ordered_set</c>.</fsummary> <desc> @@ -746,7 +779,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="lookup" arity="2"/> + <name name="lookup" arity="2" since=""/> <fsummary>Return all objects with a specified key in an ETS table. </fsummary> <desc> @@ -786,7 +819,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="lookup_element" arity="3"/> + <name name="lookup_element" arity="3" since=""/> <fsummary>Return the <c>Pos</c>:th element of all objects with a specified key in an ETS table.</fsummary> <desc> @@ -809,7 +842,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match" arity="1"/> + <name name="match" arity="1" since=""/> <fsummary>Continues matching objects in an ETS table.</fsummary> <desc> <p>Continues a match started with @@ -823,7 +856,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match" arity="2"/> + <name name="match" arity="2" since=""/> <fsummary>Match the objects in an ETS table against a pattern. </fsummary> <desc> @@ -855,7 +888,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match" arity="3"/> + <name name="match" arity="3" since=""/> <fsummary>Match the objects in an ETS table against a pattern and return part of the answers.</fsummary> <desc> @@ -870,11 +903,14 @@ ets:is_compiled_ms(Broken).</code> <seealso marker="#first/1"><c>first/1</c></seealso> and <seealso marker="#next/2"><c>next/2</c></seealso>.</p> <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p> + <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso> + to guarantee <seealso marker="#traversal">safe traversal</seealso> + for subsequent calls to <seealso marker="#match/1"><c>match/1</c></seealso>.</p> </desc> </func> <func> - <name name="match_delete" arity="2"/> + <name name="match_delete" arity="2" since=""/> <fsummary>Delete all objects that match a specified pattern from an ETS table.</fsummary> <desc> @@ -885,7 +921,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match_object" arity="1"/> + <name name="match_object" arity="1" since=""/> <fsummary>Continues matching objects in an ETS table.</fsummary> <desc> <p>Continues a match started with @@ -900,7 +936,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match_object" arity="2"/> + <name name="match_object" arity="2" since=""/> <fsummary>Match the objects in an ETS table against a pattern. </fsummary> <desc> @@ -919,7 +955,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match_object" arity="3"/> + <name name="match_object" arity="3" since=""/> <fsummary>Match the objects in an ETS table against a pattern and return part of the answers.</fsummary> <desc> @@ -935,11 +971,15 @@ ets:is_compiled_ms(Broken).</code> <seealso marker="#first/1"><c>first/1</c></seealso> and <seealso marker="#next/2"><c>next/2</c></seealso>.</p> <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p> + <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso> + to guarantee <seealso marker="#traversal">safe traversal</seealso> + for subsequent calls to <seealso marker="#match_object/1"> + <c>match_object/1</c></seealso>.</p> </desc> </func> <func> - <name name="match_spec_compile" arity="1"/> + <name name="match_spec_compile" arity="1" since=""/> <fsummary>Compile a match specification into its internal representation. </fsummary> <desc> @@ -967,7 +1007,7 @@ ets:is_compiled_ms(Broken).</code> </func> <func> - <name name="match_spec_run" arity="2"/> + <name name="match_spec_run" arity="2" since=""/> <fsummary>Perform matching, using a compiled match specification on a list of terms.</fsummary> <desc> @@ -1004,7 +1044,7 @@ ets:select(Table, MatchSpec),</code> </func> <func> - <name name="member" arity="2"/> + <name name="member" arity="2" since=""/> <fsummary>Tests for occurrence of a key in an ETS table.</fsummary> <desc> <p>Works like <seealso marker="#lookup/2"><c>lookup/2</c></seealso>, @@ -1015,7 +1055,7 @@ ets:select(Table, MatchSpec),</code> </func> <func> - <name name="new" arity="2"/> + <name name="new" arity="2" since=""/> <fsummary>Create a new ETS table.</fsummary> <desc> <p>Creates a new table and returns a table identifier that can @@ -1185,7 +1225,7 @@ ets:select(Table, MatchSpec),</code> </func> <func> - <name name="next" arity="2"/> + <name name="next" arity="2" since=""/> <fsummary>Return the next key in an ETS table.</fsummary> <desc> <p>Returns the next key <c><anno>Key2</anno></c>, following key @@ -1197,17 +1237,18 @@ ets:select(Table, MatchSpec),</code> <p>To find the first key in the table, use <seealso marker="#first/1"><c>first/1</c></seealso>.</p> <p>Unless a table of type <c>set</c>, <c>bag</c>, or - <c>duplicate_bag</c> is protected using + <c>duplicate_bag</c> is fixated using <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso>, - a traversal can fail if - concurrent updates are made to the table. For table - type <c>ordered_set</c>, the function returns the next key in - order, even if the object does no longer exist.</p> + a call to <c>next/2</c> will fail if <c><anno>Key1</anno></c> no longer + exists in the table. For table type <c>ordered_set</c>, the function + always returns the next key after <c><anno>Key1</anno></c> in term + order, regardless whether <c><anno>Key1</anno></c> ever existed in the + table.</p> </desc> </func> <func> - <name name="prev" arity="2"/> + <name name="prev" arity="2" since=""/> <fsummary>Return the previous key in an ETS table of type <c>ordered_set</c>.</fsummary> <desc> @@ -1217,13 +1258,13 @@ ets:select(Table, MatchSpec),</code> table types, the function is synonymous to <seealso marker="#next/2"><c>next/2</c></seealso>. If no previous key exists, <c>'$end_of_table'</c> is returned.</p> - <p>To find the last key in the table, use + <p>To find the last key in an <c>ordered_set</c> table, use <seealso marker="#last/1"><c>last/1</c></seealso>.</p> </desc> </func> <func> - <name name="rename" arity="2"/> + <name name="rename" arity="2" since=""/> <fsummary>Rename a named ETS table.</fsummary> <desc> <p>Renames the named table <c><anno>Tab</anno></c> to the new name @@ -1233,7 +1274,7 @@ ets:select(Table, MatchSpec),</code> </func> <func> - <name name="repair_continuation" arity="2"/> + <name name="repair_continuation" arity="2" since=""/> <fsummary>Repair a continuation from <c>ets:select/1 or ets:select/3</c> that has passed through external representation.</fsummary> <desc> @@ -1288,11 +1329,20 @@ ets:select(ets:repair_continuation(Broken,MS)).</code> </func> <func> - <name name="safe_fixtable" arity="2"/> + <name name="safe_fixtable" arity="2" since=""/> <fsummary>Fix an ETS table for safe traversal.</fsummary> <desc> <p>Fixes a table of type <c>set</c>, <c>bag</c>, or - <c>duplicate_bag</c> for safe traversal.</p> + <c>duplicate_bag</c> for <seealso marker="#traversal"> + safe traversal</seealso> using + <seealso marker="#first/1"><c>first/1</c></seealso> & + <seealso marker="#next/2"><c>next/2</c></seealso>, + <seealso marker="#match/3"><c>match/3</c></seealso> & + <seealso marker="#match/1"><c>match/1</c></seealso>, + <seealso marker="#match_object/3"><c>match_object/3</c></seealso> & + <seealso marker="#match_object/1"><c>match_object/1</c></seealso>, or + <seealso marker="#select/3"><c>select/3</c></seealso> & + <seealso marker="#select/1"><c>select/1</c></seealso>.</p> <p>A process fixes a table by calling <c>safe_fixtable(<anno>Tab</anno>, true)</c>. The table remains fixed until the process releases it by calling @@ -1305,11 +1355,11 @@ ets:select(ets:repair_continuation(Broken,MS)).</code> <p>When a table is fixed, a sequence of <seealso marker="#first/1"><c>first/1</c></seealso> and <seealso marker="#next/2"><c>next/2</c></seealso> calls are - guaranteed to succeed, and each object in - the table is returned only once, even if objects - are removed or inserted during the traversal. The keys for new - objects inserted during the traversal <em>can</em> be returned by - <c>next/2</c> (it depends on the internal ordering of the keys).</p> + guaranteed to succeed even if keys are removed during the + traversal. The keys for objects inserted or deleted during a + traversal may or may not be returned by <c>next/2</c> depending on + the ordering of keys within the table and if the key exists at the time + <c>next/2</c> is called.</p> <p><em>Example:</em></p> <code type="none"> clean_all_with_value(Tab,X) -> @@ -1327,7 +1377,7 @@ clean_all_with_value(Tab,X,Key) -> true end, clean_all_with_value(Tab,X,ets:next(Tab,Key)).</code> - <p>Notice that no deleted objects are removed from a + <p>Notice that deleted objects are not freed from a fixed table until it has been released. If a process fixes a table but never releases it, the memory used by the deleted objects is never freed. The performance of operations on @@ -1337,14 +1387,14 @@ clean_all_with_value(Tab,X,Key) -> <c>info(Tab, safe_fixed_monotonic_time)</c></seealso>. A system with many processes fixing tables can need a monitor that sends alarms when tables have been fixed for too long.</p> - <p>Notice that for table type <c>ordered_set</c>, - <c>safe_fixtable/2</c> is not necessary, as calls to - <c>first/1</c> and <c>next/2</c> always succeed.</p> + <p>Notice that <c>safe_fixtable/2</c> is not necessary for table type + <c>ordered_set</c> and for traversals done by a single ETS function call, + like <seealso marker="#select/2"><c>select/2</c></seealso>.</p> </desc> </func> <func> - <name name="select" arity="1"/> + <name name="select" arity="1" since=""/> <fsummary>Continue matching objects in an ETS table.</fsummary> <desc> <p>Continues a match started with @@ -1358,7 +1408,7 @@ clean_all_with_value(Tab,X,Key) -> </func> <func> - <name name="select" arity="2"/> + <name name="select" arity="2" since=""/> <fsummary>Match the objects in an ETS table against a match specification.</fsummary> <desc> @@ -1453,7 +1503,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> </func> <func> - <name name="select" arity="3"/> + <name name="select" arity="3" since=""/> <fsummary>Match the objects in an ETS table against a match specification and return part of the answers.</fsummary> <desc> @@ -1467,12 +1517,15 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> table, which is still faster than traversing the table object by object using <seealso marker="#first/1"><c>first/1</c></seealso> and <seealso marker="#next/2"><c>next/2</c></seealso>.</p> - <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p> + <p>If the table is empty, <c>'$end_of_table'</c> is returned.</p> + <p>Use <seealso marker="#safe_fixtable/2"><c>safe_fixtable/2</c></seealso> + to guarantee <seealso marker="#traversal">safe traversal</seealso> + for subsequent calls to <seealso marker="#select/1"><c>select/1</c></seealso>.</p> </desc> </func> <func> - <name name="select_count" arity="2"/> + <name name="select_count" arity="2" since=""/> <fsummary>Match the objects in an ETS table against a match specification and return the number of objects for which the match specification returned <c>true</c>.</fsummary> @@ -1491,7 +1544,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> </func> <func> - <name name="select_delete" arity="2"/> + <name name="select_delete" arity="2" since=""/> <fsummary>Match the objects in an ETS table against a match specification and delete objects where the match specification returns <c>true</c>.</fsummary> @@ -1515,7 +1568,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> </func> <func> - <name name="select_replace" arity="2"/> + <name name="select_replace" arity="2" since="OTP 20.0"/> <fsummary>Match and replace objects atomically in an ETS table</fsummary> <desc> <p>Matches the objects in the table <c><anno>Tab</anno></c> using a @@ -1524,7 +1577,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> the match specification result.</p> <p>The match-and-replace operation for each individual object is guaranteed to be <seealso marker="#concurrency">atomic and isolated</seealso>. The - <c>select_replace</c> table iteration as a whole, like all other select functions, + <c>select_replace</c> table traversal as a whole, like all other select functions, does not give such guarantees.</p> <p>The match specifiction must be guaranteed to <em>retain the key</em> of any matched object. If not, <c>select_replace</c> will fail with <c>badarg</c> @@ -1554,7 +1607,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="select_reverse" arity="1"/> + <name name="select_reverse" arity="1" since="OTP R14B"/> <fsummary>Continue matching objects in an ETS table.</fsummary> <desc> <p>Continues a match started with <seealso marker="#select_reverse/3"> @@ -1587,7 +1640,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="select_reverse" arity="2"/> + <name name="select_reverse" arity="2" since="OTP R14B"/> <fsummary>Match the objects in an ETS table against a match specification.</fsummary> <desc> @@ -1599,7 +1652,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="select_reverse" arity="3"/> + <name name="select_reverse" arity="3" since="OTP R14B"/> <fsummary>Match the objects in an ETS table against a match specification and return part of the answers.</fsummary> <desc> @@ -1617,7 +1670,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="setopts" arity="2"/> + <name name="setopts" arity="2" since=""/> <fsummary>Set table options.</fsummary> <desc> <p>Sets table options. The only allowed option to be set after the @@ -1628,7 +1681,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="slot" arity="2"/> + <name name="slot" arity="2" since=""/> <fsummary>Return all objects in a specified slot of an ETS table. </fsummary> <desc> @@ -1653,7 +1706,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="tab2file" arity="2"/> + <name name="tab2file" arity="2" since=""/> <fsummary>Dump an ETS table to a file.</fsummary> <desc> <p>Dumps table <c><anno>Tab</anno></c> to file @@ -1664,7 +1717,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="tab2file" arity="3"/> + <name name="tab2file" arity="3" since=""/> <fsummary>Dump an ETS table to a file.</fsummary> <desc> <p>Dumps table <c><anno>Tab</anno></c> to file @@ -1711,7 +1764,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="tab2list" arity="1"/> + <name name="tab2list" arity="1" since=""/> <fsummary>Return a list of all objects in an ETS table.</fsummary> <desc> <p>Returns a list of all objects in table <c><anno>Tab</anno></c>.</p> @@ -1719,7 +1772,7 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="tabfile_info" arity="1"/> + <name name="tabfile_info" arity="1" since=""/> <fsummary>Return a list of all objects in an ETS table.</fsummary> <desc> <p>Returns information about the table dumped to file by @@ -1797,8 +1850,8 @@ Success = (1 =:= ets:select_replace(T, [{Old, [], [{const, New}]}])), </func> <func> - <name name="table" arity="1"/> - <name name="table" arity="2"/> + <name name="table" arity="1" since=""/> + <name name="table" arity="2" since=""/> <fsummary>Return a QLC query handle.</fsummary> <desc> <p>Returns a Query List @@ -1874,7 +1927,7 @@ true</pre> </func> <func> - <name name="take" arity="2"/> + <name name="take" arity="2" since="OTP 18.0"/> <fsummary>Return and remove all objects with a specified key from an ETS table.</fsummary> <desc> @@ -1889,7 +1942,7 @@ true</pre> </desc> </func> <func> - <name name="test_ms" arity="2"/> + <name name="test_ms" arity="2" since=""/> <fsummary>Test a match specification for use in <c>select/2</c>. </fsummary> <desc> @@ -1916,7 +1969,7 @@ true</pre> </func> <func> - <name name="to_dets" arity="2"/> + <name name="to_dets" arity="2" since=""/> <fsummary>Fill a Dets table with objects from an ETS table. </fsummary> <desc> @@ -1927,12 +1980,12 @@ true</pre> </func> <func> - <name name="update_counter" arity="3" clause_i="1"/> - <name name="update_counter" arity="4" clause_i="1"/> - <name name="update_counter" arity="3" clause_i="2"/> - <name name="update_counter" arity="4" clause_i="2"/> - <name name="update_counter" arity="3" clause_i="3"/> - <name name="update_counter" arity="4" clause_i="3"/> + <name name="update_counter" arity="3" clause_i="1" since=""/> + <name name="update_counter" arity="4" clause_i="1" since="OTP 18.0"/> + <name name="update_counter" arity="3" clause_i="2" since=""/> + <name name="update_counter" arity="4" clause_i="2" since="OTP 18.0"/> + <name name="update_counter" arity="3" clause_i="3" since=""/> + <name name="update_counter" arity="4" clause_i="3" since="OTP 18.0"/> <fsummary>Update a counter object in an ETS table.</fsummary> <type variable="Tab"/> <type variable="Key"/> @@ -2011,8 +2064,8 @@ true</pre> </func> <func> - <name name="update_element" arity="3" clause_i="1"/> - <name name="update_element" arity="3" clause_i="2"/> + <name name="update_element" arity="3" clause_i="1" since=""/> + <name name="update_element" arity="3" clause_i="2" since=""/> <fsummary>Update the <c>Pos</c>:th element of the object with a specified key in an ETS table.</fsummary> <type variable="Tab"/> @@ -2054,7 +2107,7 @@ true</pre> </func> <func> - <name name="whereis" arity="1"/> + <name name="whereis" arity="1" since="OTP 21.0"/> <fsummary>Retrieves the tid() of a named table.</fsummary> <desc> <p>This function returns the diff --git a/lib/stdlib/doc/src/file_sorter.xml b/lib/stdlib/doc/src/file_sorter.xml index e988d58c2f..942d98fe61 100644 --- a/lib/stdlib/doc/src/file_sorter.xml +++ b/lib/stdlib/doc/src/file_sorter.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>file_sorter.xml</file> </header> - <module>file_sorter</module> + <module since="">file_sorter</module> <modulesummary>File sorter.</modulesummary> <description> <p>This module contains functions for sorting terms on files, merging @@ -334,8 +334,8 @@ output(L) -> <funcs> <func> - <name name="check" arity="1"/> - <name name="check" arity="2"/> + <name name="check" arity="1" since=""/> + <name name="check" arity="2" since=""/> <fsummary>Check whether terms on files are sorted.</fsummary> <desc> <p>Checks files for sortedness. If a file is not sorted, the @@ -347,8 +347,8 @@ output(L) -> </func> <func> - <name name="keycheck" arity="2"/> - <name name="keycheck" arity="3"/> + <name name="keycheck" arity="2" since=""/> + <name name="keycheck" arity="3" since=""/> <fsummary>Check whether terms on files are sorted by key.</fsummary> <desc> <p>Checks files for sortedness. If a file is not sorted, the @@ -360,8 +360,8 @@ output(L) -> </func> <func> - <name name="keymerge" arity="3"/> - <name name="keymerge" arity="4"/> + <name name="keymerge" arity="3" since=""/> + <name name="keymerge" arity="4" since=""/> <fsummary>Merge terms on files by key.</fsummary> <desc> <p>Merges tuples on files. Each input file is assumed to be @@ -372,7 +372,7 @@ output(L) -> </func> <func> - <name name="keysort" arity="2"/> + <name name="keysort" arity="2" since=""/> <fsummary>Sort terms on files by key.</fsummary> <desc> <p>Sorts tuples on files.</p> @@ -382,8 +382,8 @@ output(L) -> </func> <func> - <name name="keysort" arity="3"/> - <name name="keysort" arity="4"/> + <name name="keysort" arity="3" since=""/> + <name name="keysort" arity="4" since=""/> <fsummary>Sort terms on files by key.</fsummary> <desc> <p>Sorts tuples on files. The sort is performed on the @@ -397,8 +397,8 @@ output(L) -> </func> <func> - <name name="merge" arity="2"/> - <name name="merge" arity="3"/> + <name name="merge" arity="2" since=""/> + <name name="merge" arity="3" since=""/> <fsummary>Merge terms on files.</fsummary> <desc> <p>Merges terms on files. Each input file is assumed to be @@ -409,7 +409,7 @@ output(L) -> </func> <func> - <name name="sort" arity="1"/> + <name name="sort" arity="1" since=""/> <fsummary>Sort terms on files.</fsummary> <desc> <p>Sorts terms on files.</p> @@ -419,8 +419,8 @@ output(L) -> </func> <func> - <name name="sort" arity="2"/> - <name name="sort" arity="3"/> + <name name="sort" arity="2" since=""/> + <name name="sort" arity="3" since=""/> <fsummary>Sort terms on files.</fsummary> <desc> <p>Sorts terms on files.</p> diff --git a/lib/stdlib/doc/src/filelib.xml b/lib/stdlib/doc/src/filelib.xml index 3b5be75bc0..5df415834f 100644 --- a/lib/stdlib/doc/src/filelib.xml +++ b/lib/stdlib/doc/src/filelib.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>filelib.xml</file> </header> - <module>filelib</module> + <module since="">filelib</module> <modulesummary>File utilities, such as wildcard matching of filenames. </modulesummary> <description> @@ -94,7 +94,7 @@ <funcs> <func> - <name name="ensure_dir" arity="1"/> + <name name="ensure_dir" arity="1" since=""/> <fsummary>Ensure that all parent directories for a file or directory exist.</fsummary> <desc> @@ -108,7 +108,7 @@ </func> <func> - <name name="file_size" arity="1"/> + <name name="file_size" arity="1" since=""/> <fsummary>Return the size in bytes of a file.</fsummary> <desc> <p>Returns the size of the specified file.</p> @@ -116,7 +116,7 @@ </func> <func> - <name name="fold_files" arity="5"/> + <name name="fold_files" arity="5" since=""/> <fsummary>Fold over all files matching a regular expression.</fsummary> <desc> <p>Folds function <c><anno>Fun</anno></c> over all (regular) files @@ -142,7 +142,7 @@ </func> <func> - <name name="is_dir" arity="1"/> + <name name="is_dir" arity="1" since=""/> <fsummary>Test whether <c>Name</c> refers to a directory.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Name</anno></c> @@ -151,7 +151,7 @@ </func> <func> - <name name="is_file" arity="1"/> + <name name="is_file" arity="1" since=""/> <fsummary>Test whether <c>Name</c> refers to a file or directory. </fsummary> <desc> @@ -161,7 +161,7 @@ </func> <func> - <name name="is_regular" arity="1"/> + <name name="is_regular" arity="1" since=""/> <fsummary>Test whether <c>Name</c> refers to a (regular) file.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Name</anno></c> @@ -170,7 +170,7 @@ </func> <func> - <name name="last_modified" arity="1"/> + <name name="last_modified" arity="1" since=""/> <fsummary>Return the local date and time when a file was last modified. </fsummary> <desc> @@ -180,7 +180,7 @@ </func> <func> - <name name="wildcard" arity="1"/> + <name name="wildcard" arity="1" since=""/> <fsummary>Match filenames using Unix-style wildcards.</fsummary> <desc> <p>Returns a list of all files that match Unix-style wildcard string @@ -252,7 +252,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code> </func> <func> - <name name="wildcard" arity="2"/> + <name name="wildcard" arity="2" since=""/> <fsummary>Match filenames using Unix-style wildcards starting at a specified directory.</fsummary> <desc> @@ -263,8 +263,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code> </func> <func> - <name name="find_file" arity="2"/> - <name name="find_file" arity="3"/> + <name name="find_file" arity="2" since="OTP 20.0"/> + <name name="find_file" arity="3" since="OTP 20.0"/> <fsummary>Find a file relative to a given directory.</fsummary> <desc> <p>Looks for a file of the given name by applying suffix rules to @@ -278,7 +278,7 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code> </desc> </func> <func> - <name name="find_source" arity="1"/> + <name name="find_source" arity="1" since="OTP 20.0"/> <fsummary>Find the source file for a given object file.</fsummary> <desc> <p>Equivalent to <c>find_source(Base, Dir)</c>, where <c>Dir</c> is @@ -287,8 +287,8 @@ filelib:wildcard("lib/**/*.{erl,hrl}")</code> </desc> </func> <func> - <name name="find_source" arity="2"/> - <name name="find_source" arity="3"/> + <name name="find_source" arity="2" since="OTP 20.0"/> + <name name="find_source" arity="3" since="OTP 20.0"/> <fsummary>Find a source file relative to a given directory.</fsummary> <desc> <p>Applies file extension specific rules to find the source file for diff --git a/lib/stdlib/doc/src/filename.xml b/lib/stdlib/doc/src/filename.xml index 36254c2d00..ae42846c6b 100644 --- a/lib/stdlib/doc/src/filename.xml +++ b/lib/stdlib/doc/src/filename.xml @@ -28,7 +28,7 @@ <date>1997-11-13</date> <rev>B</rev> </header> - <module>filename</module> + <module since="">filename</module> <modulesummary>Filename manipulation functions.</modulesummary> <description> <p>This module provides functions @@ -87,7 +87,7 @@ <funcs> <func> - <name name="absname" arity="1"/> + <name name="absname" arity="1" since=""/> <fsummary>Convert a filename to an absolute name, relative the working directory.</fsummary> <desc> @@ -119,7 +119,7 @@ </func> <func> - <name name="absname" arity="2"/> + <name name="absname" arity="2" since=""/> <fsummary>Convert a filename to an absolute name, relative a specified directory.</fsummary> <desc> @@ -130,7 +130,7 @@ </func> <func> - <name name="absname_join" arity="2"/> + <name name="absname_join" arity="2" since=""/> <fsummary>Join an absolute directory with a relative filename.</fsummary> <desc> <p>Joins an absolute directory with a relative filename. Similar to @@ -144,8 +144,8 @@ </func> <func> - <name name="basedir" arity="2" clause_i="1"/> - <name name="basedir" arity="2" clause_i="2"/> + <name name="basedir" arity="2" clause_i="1" since="OTP 19.0"/> + <name name="basedir" arity="2" clause_i="2" since="OTP 19.0"/> <fsummary>Equivalent to <c>basedir(<anno>PathType</anno>, <anno>Application</anno>,#{})</c> or <c>basedir(<anno>PathsType</anno>, <anno>Application</anno>,#{})</c>. @@ -165,8 +165,8 @@ basedir(<anno>PathsType</anno>, <anno>Application</anno>, #{})</seealso>. </desc> </func> <func> - <name name="basedir" arity="3" clause_i="1" anchor="basedir_3_1"/> - <name name="basedir" arity="3" clause_i="2" anchor="basedir_3_2"/> + <name name="basedir" arity="3" clause_i="1" anchor="basedir_3_1" since="OTP 19.0"/> + <name name="basedir" arity="3" clause_i="2" anchor="basedir_3_2" since="OTP 19.0"/> <fsummary></fsummary> <type variable="PathType" name_i="1"/> <type name="basedir_path_type"/> @@ -314,7 +314,7 @@ true </desc> </func> <func> - <name name="basename" arity="1"/> + <name name="basename" arity="1" since=""/> <fsummary>Return the last component of a filename.</fsummary> <desc> <p>Returns the last component of <c><anno>Filename</anno></c>, or @@ -332,7 +332,7 @@ true </func> <func> - <name name="basename" arity="2"/> + <name name="basename" arity="2" since=""/> <fsummary>Return the last component of a filename, stripped of the specified extension.</fsummary> <desc> @@ -357,7 +357,7 @@ true </func> <func> - <name name="dirname" arity="1"/> + <name name="dirname" arity="1" since=""/> <fsummary>Return the directory part of a path name.</fsummary> <desc> <p>Returns the directory part of <c><anno>Filename</anno></c>.</p> @@ -374,7 +374,7 @@ true </func> <func> - <name name="extension" arity="1"/> + <name name="extension" arity="1" since=""/> <fsummary>Return the file extension.</fsummary> <desc> <p>Returns the file extension of <c><anno>Filename</anno></c>, @@ -390,8 +390,8 @@ true </func> <func> - <name name="find_src" arity="1"/> - <name name="find_src" arity="2"/> + <name name="find_src" arity="1" since=""/> + <name name="find_src" arity="2" since=""/> <fsummary>Find the filename and compiler options for a module.</fsummary> <desc> <p>Finds the source filename and compiler options for a module. @@ -438,7 +438,7 @@ true </func> <func> - <name name="flatten" arity="1"/> + <name name="flatten" arity="1" since=""/> <fsummary>Convert a filename to a flat string.</fsummary> <desc> <p>Converts a possibly deep list filename consisting of @@ -448,7 +448,7 @@ true </func> <func> - <name name="join" arity="1"/> + <name name="join" arity="1" since=""/> <fsummary>Join a list of filename components with directory separators. </fsummary> <desc> @@ -476,7 +476,7 @@ true </func> <func> - <name name="join" arity="2"/> + <name name="join" arity="2" since=""/> <fsummary>Join two filename components with directory separators. </fsummary> <desc> @@ -487,7 +487,7 @@ true </func> <func> - <name name="nativename" arity="1"/> + <name name="nativename" arity="1" since=""/> <fsummary>Return the native form of a file path.</fsummary> <desc> <p>Converts <c><anno>Path</anno></c> to a form accepted by the command @@ -506,7 +506,7 @@ true </func> <func> - <name name="pathtype" arity="1"/> + <name name="pathtype" arity="1" since=""/> <fsummary>Return the path type.</fsummary> <desc> <p>Returns the path type, which is one of the following:</p> @@ -536,8 +536,8 @@ true </func> <func> - <name name="rootname" arity="1"/> - <name name="rootname" arity="2"/> + <name name="rootname" arity="1" since=""/> + <name name="rootname" arity="2" since=""/> <fsummary>Remove a filename extension.</fsummary> <desc> <p>Removes a filename extension. <c>rootname/2</c> works as @@ -557,7 +557,7 @@ true </func> <func> - <name name="safe_relative_path" arity="1"/> + <name name="safe_relative_path" arity="1" since="OTP 19.3"/> <fsummary>Sanitize a relative path to avoid directory traversal attacks.</fsummary> <desc> <p>Sanitizes the relative path by eliminating ".." and "." @@ -584,7 +584,7 @@ unsafe</pre> </func> <func> - <name name="split" arity="1"/> + <name name="split" arity="1" since=""/> <fsummary>Split a filename into its path components.</fsummary> <desc> <p>Returns a list whose elements are the path components of diff --git a/lib/stdlib/doc/src/gb_sets.xml b/lib/stdlib/doc/src/gb_sets.xml index 03397b4503..a9596c6e4d 100644 --- a/lib/stdlib/doc/src/gb_sets.xml +++ b/lib/stdlib/doc/src/gb_sets.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>gb_sets</module> + <module since="">gb_sets</module> <modulesummary>General balanced trees.</modulesummary> <description> <p>This module provides ordered sets using Prof. Arne Andersson's @@ -123,8 +123,8 @@ <funcs> <func> - <name name="add" arity="2"/> - <name name="add_element" arity="2"/> + <name name="add" arity="2" since=""/> + <name name="add_element" arity="2" since=""/> <fsummary>Add a (possibly existing) element to a set.</fsummary> <desc> <p>Returns a new set formed from <c><anno>Set1</anno></c> with @@ -135,7 +135,7 @@ </func> <func> - <name name="balance" arity="1"/> + <name name="balance" arity="1" since=""/> <fsummary>Rebalance tree representation of a set.</fsummary> <desc> <p>Rebalances the tree representation of <c><anno>Set1</anno></c>. @@ -149,7 +149,7 @@ </func> <func> - <name name="del_element" arity="2"/> + <name name="del_element" arity="2" since=""/> <fsummary>Remove a (possibly non-existing) element from a set.</fsummary> <desc> <p>Returns a new set formed from <c><anno>Set1</anno></c> with @@ -160,7 +160,7 @@ </func> <func> - <name name="delete" arity="2"/> + <name name="delete" arity="2" since=""/> <fsummary>Remove an element from a set.</fsummary> <desc> <p>Returns a new set formed from <c><anno>Set1</anno></c> with @@ -171,7 +171,7 @@ </func> <func> - <name name="delete_any" arity="2"/> + <name name="delete_any" arity="2" since=""/> <fsummary>Remove a (possibly non-existing) element from a set.</fsummary> <desc> <p>Returns a new set formed from <c><anno>Set1</anno></c> with @@ -182,7 +182,7 @@ </func> <func> - <name name="difference" arity="2"/> + <name name="difference" arity="2" since=""/> <fsummary>Return the difference of two sets.</fsummary> <desc> <p>Returns only the elements of <c><anno>Set1</anno></c> that are not @@ -191,7 +191,7 @@ </func> <func> - <name name="empty" arity="0"/> + <name name="empty" arity="0" since=""/> <fsummary>Return an empty set.</fsummary> <desc> <p>Returns a new empty set.</p> @@ -199,7 +199,7 @@ </func> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Filter set elements.</fsummary> <desc> <p>Filters elements in <c><anno>Set1</anno></c> using predicate function @@ -208,7 +208,7 @@ </func> <func> - <name name="fold" arity="3"/> + <name name="fold" arity="3" since=""/> <fsummary>Fold over set elements.</fsummary> <desc> <p>Folds <c><anno>Function</anno></c> over every element in @@ -218,7 +218,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Convert a list into a set.</fsummary> <desc> <p>Returns a set of the elements in <c><anno>List</anno></c>, where @@ -227,7 +227,7 @@ </func> <func> - <name name="from_ordset" arity="1"/> + <name name="from_ordset" arity="1" since=""/> <fsummary>Make a set from an ordset list.</fsummary> <desc> <p>Turns an ordered-set list <c><anno>List</anno></c> into a set. @@ -236,7 +236,7 @@ </func> <func> - <name name="insert" arity="2"/> + <name name="insert" arity="2" since=""/> <fsummary>Add a new element to a set.</fsummary> <desc> <p>Returns a new set formed from <c><anno>Set1</anno></c> with @@ -247,7 +247,7 @@ </func> <func> - <name name="intersection" arity="1"/> + <name name="intersection" arity="1" since=""/> <fsummary>Return the intersection of a list of sets.</fsummary> <desc> <p>Returns the intersection of the non-empty list of sets.</p> @@ -255,7 +255,7 @@ </func> <func> - <name name="intersection" arity="2"/> + <name name="intersection" arity="2" since=""/> <fsummary>Return the intersection of two sets.</fsummary> <desc> <p>Returns the intersection of <c><anno>Set1</anno></c> and @@ -264,7 +264,7 @@ </func> <func> - <name name="is_disjoint" arity="2"/> + <name name="is_disjoint" arity="2" since=""/> <fsummary>Check whether two sets are disjoint.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set1</anno></c> and @@ -274,7 +274,7 @@ </func> <func> - <name name="is_element" arity="2"/> + <name name="is_element" arity="2" since=""/> <fsummary>Test for membership of a set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of @@ -283,7 +283,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since=""/> <fsummary>Test for empty set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set, @@ -292,7 +292,7 @@ </func> <func> - <name name="is_member" arity="2"/> + <name name="is_member" arity="2" since=""/> <fsummary>Test for membership of a set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of @@ -301,7 +301,7 @@ </func> <func> - <name name="is_set" arity="1"/> + <name name="is_set" arity="1" since=""/> <fsummary>Test for a set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> appears to be a set, @@ -310,7 +310,7 @@ </func> <func> - <name name="is_subset" arity="2"/> + <name name="is_subset" arity="2" since=""/> <fsummary>Test for subset.</fsummary> <desc> <p>Returns <c>true</c> when every element of <c><anno>Set1</anno></c> is @@ -319,7 +319,7 @@ </func> <func> - <name name="iterator" arity="1"/> + <name name="iterator" arity="1" since=""/> <fsummary>Return an iterator for a set.</fsummary> <desc> <p>Returns an iterator that can be used for traversing the entries of @@ -336,7 +336,7 @@ </func> <func> - <name name="iterator_from" arity="2"/> + <name name="iterator_from" arity="2" since="OTP 18.0"/> <fsummary>Return an iterator for a set starting from a specified element. </fsummary> <desc> @@ -351,7 +351,7 @@ </func> <func> - <name name="largest" arity="1"/> + <name name="largest" arity="1" since=""/> <fsummary>Return largest element.</fsummary> <desc> <p>Returns the largest element in <c><anno>Set</anno></c>. Assumes that @@ -360,7 +360,7 @@ </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Return an empty set.</fsummary> <desc> <p>Returns a new empty set.</p> @@ -368,7 +368,7 @@ </func> <func> - <name name="next" arity="1"/> + <name name="next" arity="1" since=""/> <fsummary>Traverse a set with an iterator.</fsummary> <desc> <p>Returns <c>{<anno>Element</anno>, <anno>Iter2</anno>}</c>, where @@ -381,7 +381,7 @@ </func> <func> - <name name="singleton" arity="1"/> + <name name="singleton" arity="1" since=""/> <fsummary>Return a set with one element.</fsummary> <desc> <p>Returns a set containing only element <c><anno>Element</anno></c>. @@ -390,7 +390,7 @@ </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Return the number of elements in a set.</fsummary> <desc> <p>Returns the number of elements in <c><anno>Set</anno></c>.</p> @@ -398,7 +398,7 @@ </func> <func> - <name name="smallest" arity="1"/> + <name name="smallest" arity="1" since=""/> <fsummary>Return smallest element.</fsummary> <desc> <p>Returns the smallest element in <c><anno>Set</anno></c>. Assumes that @@ -407,7 +407,7 @@ </func> <func> - <name name="subtract" arity="2"/> + <name name="subtract" arity="2" since=""/> <fsummary>Return the difference of two sets.</fsummary> <desc> <p>Returns only the elements of <c><anno>Set1</anno></c> that are not @@ -416,7 +416,7 @@ </func> <func> - <name name="take_largest" arity="1"/> + <name name="take_largest" arity="1" since=""/> <fsummary>Extract largest element.</fsummary> <desc> <p>Returns <c>{<anno>Element</anno>, <anno>Set2</anno>}</c>, where @@ -428,7 +428,7 @@ </func> <func> - <name name="take_smallest" arity="1"/> + <name name="take_smallest" arity="1" since=""/> <fsummary>Extract smallest element.</fsummary> <desc> <p>Returns <c>{<anno>Element</anno>, <anno>Set2</anno>}</c>, where @@ -440,7 +440,7 @@ </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert a set into a list.</fsummary> <desc> <p>Returns the elements of <c><anno>Set</anno></c> as a list.</p> @@ -448,7 +448,7 @@ </func> <func> - <name name="union" arity="1"/> + <name name="union" arity="1" since=""/> <fsummary>Return the union of a list of sets.</fsummary> <desc> <p>Returns the merged (union) set of the list of sets.</p> @@ -456,7 +456,7 @@ </func> <func> - <name name="union" arity="2"/> + <name name="union" arity="2" since=""/> <fsummary>Return the union of two sets.</fsummary> <desc> <p>Returns the merged (union) set of <c><anno>Set1</anno></c> and diff --git a/lib/stdlib/doc/src/gb_trees.xml b/lib/stdlib/doc/src/gb_trees.xml index 5cfff021c1..570c9c7cb6 100644 --- a/lib/stdlib/doc/src/gb_trees.xml +++ b/lib/stdlib/doc/src/gb_trees.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>gb_trees</module> + <module since="">gb_trees</module> <modulesummary>General balanced trees.</modulesummary> <description> <p>This module provides Prof. Arne Andersson's General @@ -75,7 +75,7 @@ <funcs> <func> - <name name="balance" arity="1"/> + <name name="balance" arity="1" since=""/> <fsummary>Rebalance a tree.</fsummary> <desc> <p>Rebalances <c><anno>Tree1</anno></c>. Notice that this is @@ -88,7 +88,7 @@ </func> <func> - <name name="delete" arity="2"/> + <name name="delete" arity="2" since=""/> <fsummary>Remove a node from a tree.</fsummary> <desc> <p>Removes the node with key <c><anno>Key</anno></c> from @@ -98,7 +98,7 @@ </func> <func> - <name name="delete_any" arity="2"/> + <name name="delete_any" arity="2" since=""/> <fsummary>Remove a (possibly non-existing) node from a tree.</fsummary> <desc> <p>Removes the node with key <c><anno>Key</anno></c> from @@ -109,7 +109,7 @@ </func> <func> - <name name="take" arity="2"/> + <name name="take" arity="2" since="OTP 20.0"/> <fsummary>Returns a value and new tree without node with key <c>Key</c>.</fsummary> <desc> <p>Returns a value <c><anno>Value</anno></c> from node with key <c><anno>Key</anno></c> @@ -120,7 +120,7 @@ </func> <func> - <name name="take_any" arity="2"/> + <name name="take_any" arity="2" since="OTP 20.0"/> <fsummary>Returns a value and new tree without node with key <c>Key</c>.</fsummary> <desc> <p>Returns a value <c><anno>Value</anno></c> from node with key <c><anno>Key</anno></c> @@ -131,7 +131,7 @@ </func> <func> - <name name="empty" arity="0"/> + <name name="empty" arity="0" since=""/> <fsummary>Return an empty tree.</fsummary> <desc> <p>Returns a new empty tree.</p> @@ -139,7 +139,7 @@ </func> <func> - <name name="enter" arity="3"/> + <name name="enter" arity="3" since=""/> <fsummary>Insert or update key with value in a tree.</fsummary> <desc> <p>Inserts <c><anno>Key</anno></c> with value <c><anno>Value</anno></c> @@ -151,7 +151,7 @@ </func> <func> - <name name="from_orddict" arity="1"/> + <name name="from_orddict" arity="1" since=""/> <fsummary>Make a tree from an orddict.</fsummary> <desc> <p>Turns an ordered list <c><anno>List</anno></c> of key-value tuples @@ -160,7 +160,7 @@ </func> <func> - <name name="get" arity="2"/> + <name name="get" arity="2" since=""/> <fsummary>Look up a key in a tree, if present.</fsummary> <desc> <p>Retrieves the value stored with <c><anno>Key</anno></c> in @@ -171,7 +171,7 @@ </func> <func> - <name name="insert" arity="3"/> + <name name="insert" arity="3" since=""/> <fsummary>Insert a new key and value in a tree.</fsummary> <desc> <p>Inserts <c><anno>Key</anno></c> with value <c><anno>Value</anno></c> @@ -182,7 +182,7 @@ </func> <func> - <name name="is_defined" arity="2"/> + <name name="is_defined" arity="2" since=""/> <fsummary>Test for membership of a tree.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Key</anno></c> is present in @@ -191,7 +191,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since=""/> <fsummary>Test for empty tree.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Tree</anno></c> is an empty tree, @@ -200,7 +200,7 @@ </func> <func> - <name name="iterator" arity="1"/> + <name name="iterator" arity="1" since=""/> <fsummary>Return an iterator for a tree.</fsummary> <desc> <p>Returns an iterator that can be used for traversing the @@ -218,7 +218,7 @@ </func> <func> - <name name="iterator_from" arity="2"/> + <name name="iterator_from" arity="2" since="OTP 18.0"/> <fsummary>Return an iterator for a tree starting from a specified key. </fsummary> <desc> @@ -233,7 +233,7 @@ </func> <func> - <name name="keys" arity="1"/> + <name name="keys" arity="1" since=""/> <fsummary>Return a list of the keys in a tree.</fsummary> <desc> <p>Returns the keys in <c><anno>Tree</anno></c> as an ordered list.</p> @@ -241,7 +241,7 @@ </func> <func> - <name name="largest" arity="1"/> + <name name="largest" arity="1" since=""/> <fsummary>Return largest key and value.</fsummary> <desc> <p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>}</c>, where @@ -253,7 +253,7 @@ </func> <func> - <name name="lookup" arity="2"/> + <name name="lookup" arity="2" since=""/> <fsummary>Look up a key in a tree.</fsummary> <desc> <p>Looks up <c><anno>Key</anno></c> in <c><anno>Tree</anno></c>. @@ -263,7 +263,7 @@ </func> <func> - <name name="map" arity="2"/> + <name name="map" arity="2" since=""/> <fsummary>Return largest key and value.</fsummary> <desc> <p>Maps function F(<anno>K</anno>, <anno>V1</anno>) -> <anno>V2</anno> @@ -275,7 +275,7 @@ </func> <func> - <name name="next" arity="1"/> + <name name="next" arity="1" since=""/> <fsummary>Traverse a tree with an iterator.</fsummary> <desc> <p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>, @@ -288,7 +288,7 @@ </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Return the number of nodes in a tree.</fsummary> <desc> <p>Returns the number of nodes in <c><anno>Tree</anno></c>.</p> @@ -296,7 +296,7 @@ </func> <func> - <name name="smallest" arity="1"/> + <name name="smallest" arity="1" since=""/> <fsummary>Return smallest key and value.</fsummary> <desc> <p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>}</c>, where @@ -308,7 +308,7 @@ </func> <func> - <name name="take_largest" arity="1"/> + <name name="take_largest" arity="1" since=""/> <fsummary>Extract largest key and value.</fsummary> <desc> <p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>, @@ -321,7 +321,7 @@ </func> <func> - <name name="take_smallest" arity="1"/> + <name name="take_smallest" arity="1" since=""/> <fsummary>Extract smallest key and value.</fsummary> <desc> <p>Returns <c>{<anno>Key</anno>, <anno>Value</anno>, @@ -334,7 +334,7 @@ </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert a tree into a list.</fsummary> <desc> <p>Converts a tree into an ordered list of key-value tuples.</p> @@ -342,7 +342,7 @@ </func> <func> - <name name="update" arity="3"/> + <name name="update" arity="3" since=""/> <fsummary>Update a key to new value in a tree.</fsummary> <desc> <p>Updates <c><anno>Key</anno></c> to value <c><anno>Value</anno></c> @@ -352,7 +352,7 @@ </func> <func> - <name name="values" arity="1"/> + <name name="values" arity="1" since=""/> <fsummary>Return a list of the values in a tree.</fsummary> <desc> <p>Returns the values in <c><anno>Tree</anno></c> as an ordered list, diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml index fc34e51216..2915c4f507 100644 --- a/lib/stdlib/doc/src/gen_event.xml +++ b/lib/stdlib/doc/src/gen_event.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>gen_event</module> + <module since="">gen_event</module> <modulesummary>Generic event handling behavior.</modulesummary> <description> <p>This behavior module provides event handling functionality. It @@ -130,7 +130,7 @@ gen_event:stop -----> Module:terminate/2 <funcs> <func> - <name>add_handler(EventMgrRef, Handler, Args) -> Result</name> + <name since="">add_handler(EventMgrRef, Handler, Args) -> Result</name> <fsummary>Add an event handler to a generic event manager.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -178,7 +178,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>add_sup_handler(EventMgrRef, Handler, Args) -> Result</name> + <name since="">add_sup_handler(EventMgrRef, Handler, Args) -> Result</name> <fsummary>Add a supervised event handler to a generic event manager. </fsummary> <type> @@ -241,8 +241,8 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>call(EventMgrRef, Handler, Request) -> Result</name> - <name>call(EventMgrRef, Handler, Request, Timeout) -> Result</name> + <name since="">call(EventMgrRef, Handler, Request) -> Result</name> + <name since="">call(EventMgrRef, Handler, Request, Timeout) -> Result</name> <fsummary>Make a synchronous call to a generic event manager.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -285,7 +285,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>delete_handler(EventMgrRef, Handler, Args) -> Result</name> + <name since="">delete_handler(EventMgrRef, Handler, Args) -> Result</name> <fsummary>Delete an event handler from a generic event manager.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -318,8 +318,8 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>notify(EventMgrRef, Event) -> ok</name> - <name>sync_notify(EventMgrRef, Event) -> ok</name> + <name since="">notify(EventMgrRef, Event) -> ok</name> + <name since="">sync_notify(EventMgrRef, Event) -> ok</name> <fsummary>Notify an event manager about an event.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -349,9 +349,9 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>start() -> Result</name> - <name>start(EventMgrName | Options) -> Result</name> - <name>start(EventMgrName, Options) -> Result</name> + <name since="">start() -> Result</name> + <name since="">start(EventMgrName | Options) -> Result</name> + <name since="OTP 20.0">start(EventMgrName, Options) -> Result</name> <fsummary>Create a stand-alone event manager process.</fsummary> <type> <v>EventMgrName = {local,Name} | {global,GlobalName} | {via,Module,ViaName}</v> @@ -375,9 +375,9 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>start_link() -> Result</name> - <name>start_link(EventMgrName | Options) -> Result</name> - <name>start_link(EventMgrName, Options) -> Result</name> + <name since="">start_link() -> Result</name> + <name since="">start_link(EventMgrName | Options) -> Result</name> + <name since="OTP 20.0">start_link(EventMgrName, Options) -> Result</name> <fsummary>Create a generic event manager process in a supervision tree. </fsummary> <type> @@ -436,8 +436,8 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>stop(EventMgrRef) -> ok</name> - <name>stop(EventMgrRef, Reason, Timeout) -> ok</name> + <name since="">stop(EventMgrRef) -> ok</name> + <name since="OTP 18.0">stop(EventMgrRef, Reason, Timeout) -> ok</name> <fsummary>Terminate a generic event manager.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -474,7 +474,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name> + <name since="">swap_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name> <fsummary>Replace an event handler in a generic event manager.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -521,7 +521,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name> + <name since="">swap_sup_handler(EventMgrRef, {Handler1,Args1}, {Handler2,Args2}) -> Result</name> <fsummary>Replace an event handler in a generic event manager.</fsummary> <type> <v>EventMgrRef = Name | {Name,Node} | {global,GlobalName} @@ -546,7 +546,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>which_handlers(EventMgrRef) -> [Handler]</name> + <name since="">which_handlers(EventMgrRef) -> [Handler]</name> <fsummary>Return all event handlers installed in a generic event manager. </fsummary> <type> @@ -575,7 +575,7 @@ gen_event:stop -----> Module:terminate/2 <funcs> <func> - <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name> + <name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState}</name> <fsummary>Update the internal state during upgrade/downgrade.</fsummary> <type> <v>OldVsn = Vsn | {down, Vsn}</v> @@ -611,7 +611,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>Module:format_status(Opt, [PDict, State]) -> Status</name> + <name since="OTP R14B">Module:format_status(Opt, [PDict, State]) -> Status</name> <fsummary>Optional function for providing a term describing the current event handler state.</fsummary> <type> @@ -667,7 +667,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>Module:handle_call(Request, State) -> Result</name> + <name since="">Module:handle_call(Request, State) -> Result</name> <fsummary>Handle a synchronous request.</fsummary> <type> <v>Request = term()</v> @@ -698,7 +698,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>Module:handle_event(Event, State) -> Result</name> + <name since="">Module:handle_event(Event, State) -> Result</name> <fsummary>Handle an event.</fsummary> <type> <v>Event = term()</v> @@ -756,7 +756,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>Module:handle_info(Info, State) -> Result</name> + <name since="">Module:handle_info(Info, State) -> Result</name> <fsummary>Handle an incoming message.</fsummary> <type> <v>Info = term()</v> @@ -788,7 +788,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}</name> + <name since="">Module:init(InitArgs) -> {ok,State} | {ok,State,hibernate} | {error,Reason}</name> <fsummary>Initialize an event handler.</fsummary> <type> <v>InitArgs = Args | {Args,Term}</v> @@ -825,7 +825,7 @@ gen_event:stop -----> Module:terminate/2 </func> <func> - <name>Module:terminate(Arg, State) -> term()</name> + <name since="">Module:terminate(Arg, State) -> term()</name> <fsummary>Clean up before deletion.</fsummary> <type> <v>Arg = Args | {stop,Reason} | stop | remove_handler</v> diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 106bda85f5..a4554d7657 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>gen_server</module> + <module since="">gen_server</module> <modulesummary>Generic server behavior.</modulesummary> <description> <p>This behavior module provides the server of a client-server @@ -101,8 +101,8 @@ gen_server:abcast -----> Module:handle_cast/2 <funcs> <func> - <name>abcast(Name, Request) -> abcast</name> - <name>abcast(Nodes, Name, Request) -> abcast</name> + <name since="">abcast(Name, Request) -> abcast</name> + <name since="">abcast(Nodes, Name, Request) -> abcast</name> <fsummary>Send an asynchronous request to many generic servers.</fsummary> <type> <v>Nodes = [Node]</v> @@ -124,8 +124,8 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>call(ServerRef, Request) -> Reply</name> - <name>call(ServerRef, Request, Timeout) -> Reply</name> + <name since="">call(ServerRef, Request) -> Reply</name> + <name since="">call(ServerRef, Request, Timeout) -> Reply</name> <fsummary>Make a synchronous call to a generic server.</fsummary> <type> <v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v> @@ -175,7 +175,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>cast(ServerRef, Request) -> ok</name> + <name since="">cast(ServerRef, Request) -> ok</name> <fsummary>Send an asynchronous request to a generic server.</fsummary> <type> <v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v> @@ -200,10 +200,10 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>enter_loop(Module, Options, State)</name> - <name>enter_loop(Module, Options, State, ServerName)</name> - <name>enter_loop(Module, Options, State, Timeout)</name> - <name>enter_loop(Module, Options, State, ServerName, Timeout)</name> + <name since="">enter_loop(Module, Options, State)</name> + <name since="">enter_loop(Module, Options, State, ServerName)</name> + <name since="">enter_loop(Module, Options, State, Timeout)</name> + <name since="">enter_loop(Module, Options, State, ServerName, Timeout)</name> <fsummary>Enter the <c>gen_server</c> receive loop.</fsummary> <type> <v>Module = atom()</v> @@ -248,9 +248,9 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>multi_call(Name, Request) -> Result</name> - <name>multi_call(Nodes, Name, Request) -> Result</name> - <name>multi_call(Nodes, Name, Request, Timeout) -> Result</name> + <name since="">multi_call(Name, Request) -> Result</name> + <name since="">multi_call(Nodes, Name, Request) -> Result</name> + <name since="">multi_call(Nodes, Name, Request, Timeout) -> Result</name> <fsummary>Make a synchronous call to many generic servers.</fsummary> <type> <v>Nodes = [Node]</v> @@ -307,7 +307,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>reply(Client, Reply) -> Result</name> + <name since="">reply(Client, Reply) -> Result</name> <fsummary>Send a reply to a client.</fsummary> <type> <v>Client - see below</v> @@ -332,8 +332,8 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>start(Module, Args, Options) -> Result</name> - <name>start(ServerName, Module, Args, Options) -> Result</name> + <name since="">start(Module, Args, Options) -> Result</name> + <name since="">start(ServerName, Module, Args, Options) -> Result</name> <fsummary>Create a standalone <c>gen_server</c> process.</fsummary> <type> <v>ServerName = {local,Name} | {global,GlobalName}</v> @@ -361,8 +361,8 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>start_link(Module, Args, Options) -> Result</name> - <name>start_link(ServerName, Module, Args, Options) -> Result</name> + <name since="">start_link(Module, Args, Options) -> Result</name> + <name since="">start_link(ServerName, Module, Args, Options) -> Result</name> <fsummary>Create a <c>gen_server</c> process in a supervision tree. </fsummary> <type> @@ -466,8 +466,8 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>stop(ServerRef) -> ok</name> - <name>stop(ServerRef, Reason, Timeout) -> ok</name> + <name since="OTP 18.0">stop(ServerRef) -> ok</name> + <name since="OTP 18.0">stop(ServerRef, Reason, Timeout) -> ok</name> <fsummary>Synchronously stop a generic server.</fsummary> <type> <v>ServerRef = Name | {Name,Node} | {global,GlobalName}</v> @@ -508,7 +508,7 @@ gen_server:abcast -----> Module:handle_cast/2 <funcs> <func> - <name>Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name> + <name since="">Module:code_change(OldVsn, State, Extra) -> {ok, NewState} | {error, Reason}</name> <fsummary>Update the internal state during upgrade/downgrade.</fsummary> <type> <v>OldVsn = Vsn | {down, Vsn}</v> @@ -550,7 +550,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:format_status(Opt, [PDict, State]) -> Status</name> + <name since="OTP R13B04">Module:format_status(Opt, [PDict, State]) -> Status</name> <fsummary>Optional function for providing a term describing the current <c>gen_server</c> status.</fsummary> <type> @@ -610,7 +610,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:handle_call(Request, From, State) -> Result</name> + <name since="">Module:handle_call(Request, From, State) -> Result</name> <fsummary>Handle a synchronous request.</fsummary> <type> <v>Request = term()</v> @@ -677,7 +677,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:handle_cast(Request, State) -> Result</name> + <name since="">Module:handle_cast(Request, State) -> Result</name> <fsummary>Handle an asynchronous request.</fsummary> <type> <v>Request = term()</v> @@ -703,7 +703,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:handle_continue(Continue, State) -> Result</name> + <name since="OTP 21.0">Module:handle_continue(Continue, State) -> Result</name> <fsummary>Handle a continue instruction.</fsummary> <type> <v>Continue = term()</v> @@ -738,7 +738,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:handle_info(Info, State) -> Result</name> + <name since="">Module:handle_info(Info, State) -> Result</name> <fsummary>Handle an incoming message.</fsummary> <type> <v>Info = timeout | term()</v> @@ -770,7 +770,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:init(Args) -> Result</name> + <name since="">Module:init(Args) -> Result</name> <fsummary>Initialize process and internal state.</fsummary> <type> <v>Args = term()</v> @@ -811,7 +811,7 @@ gen_server:abcast -----> Module:handle_cast/2 </func> <func> - <name>Module:terminate(Reason, State)</name> + <name since="">Module:terminate(Reason, State)</name> <fsummary>Clean up before termination.</fsummary> <type> <v>Reason = normal | shutdown | {shutdown,term()} | term()</v> diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index dfecd235c9..d678d0436b 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>gen_statem</module> + <module since="OTP 19.0">gen_statem</module> <modulesummary>Generic state machine behavior.</modulesummary> <description> <p> @@ -1416,8 +1416,8 @@ handle_event(_, _, State, Data) -> <funcs> <func> - <name name="call" arity="2"/> - <name name="call" arity="3"/> + <name name="call" arity="2" since="OTP 19.0"/> + <name name="call" arity="3" since="OTP 19.0"/> <fsummary>Make a synchronous call to a <c>gen_statem</c>.</fsummary> <desc> <p> @@ -1493,7 +1493,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="cast" arity="2"/> + <name name="cast" arity="2" since="OTP 19.0"/> <fsummary>Send an asynchronous event to a <c>gen_statem</c>.</fsummary> <desc> <p> @@ -1513,7 +1513,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="4"/> + <name name="enter_loop" arity="4" since="OTP 19.1"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> @@ -1527,7 +1527,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="5"/> + <name name="enter_loop" arity="5" since="OTP 19.0"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> @@ -1551,7 +1551,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="6"/> + <name name="enter_loop" arity="6" since="OTP 19.0"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> @@ -1608,8 +1608,8 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="reply" arity="1"/> - <name name="reply" arity="2"/> + <name name="reply" arity="1" since="OTP 19.0"/> + <name name="reply" arity="2" since="OTP 19.0"/> <fsummary>Reply to a caller.</fsummary> <desc> <p> @@ -1641,8 +1641,8 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="start" arity="3"/> - <name name="start" arity="4"/> + <name name="start" arity="3" since="OTP 19.0"/> + <name name="start" arity="4" since="OTP 19.0"/> <fsummary>Create a standalone <c>gen_statem</c> process.</fsummary> <desc> <p> @@ -1662,8 +1662,8 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="start_link" arity="3"/> - <name name="start_link" arity="4"/> + <name name="start_link" arity="3" since="OTP 19.0"/> + <name name="start_link" arity="4" since="OTP 19.0"/> <fsummary>Create a linked <c>gen_statem</c> process.</fsummary> <desc> <p> @@ -1770,7 +1770,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="stop" arity="1"/> + <name name="stop" arity="1" since="OTP 19.0"/> <fsummary>Synchronously stop a generic server.</fsummary> <desc> <p> @@ -1781,7 +1781,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="stop" arity="3"/> + <name name="stop" arity="3" since="OTP 19.0"/> <fsummary>Synchronously stop a generic server.</fsummary> <desc> <p> @@ -1827,7 +1827,7 @@ handle_event(_, _, State, Data) -> <funcs> <func> - <name>Module:callback_mode() -> CallbackMode</name> + <name since="OTP 19.1">Module:callback_mode() -> CallbackMode</name> <fsummary>Update the internal state during upgrade/downgrade.</fsummary> <type> <v> @@ -1878,7 +1878,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name>Module:code_change(OldVsn, OldState, OldData, Extra) -> + <name since="OTP 19.0">Module:code_change(OldVsn, OldState, OldData, Extra) -> Result </name> <fsummary>Update the internal state during upgrade/downgrade.</fsummary> @@ -1967,7 +1967,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name>Module:init(Args) -> Result(StateType)</name> + <name since="OTP 19.0">Module:init(Args) -> Result(StateType)</name> <fsummary> Initializing process and internal state. </fsummary> @@ -2009,7 +2009,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre> </func> <func> - <name>Module:format_status(Opt, [PDict,State,Data]) -> + <name since="OTP 19.0">Module:format_status(Opt, [PDict,State,Data]) -> Status </name> <fsummary>Optional function for providing a term describing the @@ -2108,16 +2108,16 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre> </func> <func> - <name>Module:StateName(enter, OldState, Data) -> + <name since="OTP 19.0">Module:StateName(enter, OldState, Data) -> StateEnterResult(StateName) </name> - <name>Module:StateName(EventType, EventContent, Data) -> + <name since="OTP 19.0">Module:StateName(EventType, EventContent, Data) -> StateFunctionResult </name> - <name>Module:handle_event(enter, OldState, State, Data) -> + <name since="OTP 19.0">Module:handle_event(enter, OldState, State, Data) -> StateEnterResult(State) </name> - <name>Module:handle_event(EventType, EventContent, State, Data) -> + <name since="OTP 19.0">Module:handle_event(EventType, EventContent, State, Data) -> HandleEventResult </name> <fsummary>Handle an event.</fsummary> @@ -2236,7 +2236,7 @@ init(Args) -> erlang:error(not_implemented, [Args]).</pre> </func> <func> - <name>Module:terminate(Reason, State, Data) -> Ignored</name> + <name since="OTP 19.0">Module:terminate(Reason, State, Data) -> Ignored</name> <fsummary>Clean up before termination.</fsummary> <type> <v>Reason = normal | shutdown | {shutdown,term()} | term()</v> diff --git a/lib/stdlib/doc/src/io.xml b/lib/stdlib/doc/src/io.xml index d4a2713840..d69e808586 100644 --- a/lib/stdlib/doc/src/io.xml +++ b/lib/stdlib/doc/src/io.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>io</module> + <module since="">io</module> <modulesummary>Standard I/O server interface functions.</modulesummary> <description> <p>This module provides an interface to standard Erlang I/O servers. @@ -104,8 +104,8 @@ <funcs> <func> - <name name="columns" arity="0"/> - <name name="columns" arity="1"/> + <name name="columns" arity="0" since=""/> + <name name="columns" arity="1" since=""/> <fsummary>Get the number of columns of an I/O device.</fsummary> <desc> <p>Retrieves the number of columns of the @@ -116,12 +116,12 @@ </func> <func> - <name name="format" arity="1"/> - <name name="format" arity="2"/> - <name name="format" arity="3"/> - <name name="fwrite" arity="1"/> - <name name="fwrite" arity="2"/> - <name name="fwrite" arity="3"/> + <name name="format" arity="1" since=""/> + <name name="format" arity="2" since=""/> + <name name="format" arity="3" since=""/> + <name name="fwrite" arity="1" since=""/> + <name name="fwrite" arity="2" since=""/> + <name name="fwrite" arity="3" since=""/> <fsummary>Write formatted output.</fsummary> <desc> <p>Writes the items in <c><anno>Data</anno></c> (<c>[]</c>) on the @@ -523,8 +523,8 @@ ok </func> <func> - <name name="fread" arity="2"/> - <name name="fread" arity="3"/> + <name name="fread" arity="2" since=""/> + <name name="fread" arity="3" since=""/> <fsummary>Read formatted input.</fsummary> <type name="server_no_data"/> <desc> @@ -690,8 +690,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in </func> <func> - <name name="get_chars" arity="2"/> - <name name="get_chars" arity="3"/> + <name name="get_chars" arity="2" since=""/> + <name name="get_chars" arity="3" since=""/> <fsummary>Read a specified number of characters.</fsummary> <type name="server_no_data"/> <desc> @@ -722,8 +722,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in </func> <func> - <name name="get_line" arity="1"/> - <name name="get_line" arity="2"/> + <name name="get_line" arity="1" since=""/> + <name name="get_line" arity="2" since=""/> <fsummary>Read a line.</fsummary> <type name="server_no_data"/> <desc> @@ -754,8 +754,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in </func> <func> - <name name="getopts" arity="0"/> - <name name="getopts" arity="1"/> + <name name="getopts" arity="0" since=""/> + <name name="getopts" arity="1" since=""/> <fsummary>Get the supported options and values from an I/O server. </fsummary> <desc> @@ -781,8 +781,8 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in </func> <func> - <name name="nl" arity="0"/> - <name name="nl" arity="1"/> + <name name="nl" arity="0" since=""/> + <name name="nl" arity="1" since=""/> <fsummary>Write a newline.</fsummary> <desc> <p>Writes new line to the standard output @@ -791,10 +791,10 @@ enter><input>:</input> <input>alan</input> <input>:</input> <input>joe</in </func> <func> - <name name="parse_erl_exprs" arity="1"/> - <name name="parse_erl_exprs" arity="2"/> - <name name="parse_erl_exprs" arity="3"/> - <name name="parse_erl_exprs" arity="4"/> + <name name="parse_erl_exprs" arity="1" since=""/> + <name name="parse_erl_exprs" arity="2" since=""/> + <name name="parse_erl_exprs" arity="3" since=""/> + <name name="parse_erl_exprs" arity="4" since="OTP R16B"/> <fsummary>Read, tokenize, and parse Erlang expressions.</fsummary> <type name="parse_ret"/> <type name="server_no_data"/> @@ -844,10 +844,10 @@ enter><input>abc("hey".</input> </func> <func> - <name name="parse_erl_form" arity="1"/> - <name name="parse_erl_form" arity="2"/> - <name name="parse_erl_form" arity="3"/> - <name name="parse_erl_form" arity="4"/> + <name name="parse_erl_form" arity="1" since=""/> + <name name="parse_erl_form" arity="2" since=""/> + <name name="parse_erl_form" arity="3" since=""/> + <name name="parse_erl_form" arity="4" since="OTP R16B"/> <fsummary>Read, tokenize, and parse an Erlang form.</fsummary> <type name="parse_form_ret"/> <type name="server_no_data"/> @@ -888,7 +888,7 @@ enter><input>abc("hey".</input> </func> <func> - <name name="printable_range" arity="0"/> + <name name="printable_range" arity="0" since="OTP R16B"/> <fsummary>Get user-requested printable character range.</fsummary> <desc> <p>Returns the user-requested range of printable Unicode characters.</p> @@ -918,8 +918,8 @@ enter><input>abc("hey".</input> </func> <func> - <name name="put_chars" arity="1"/> - <name name="put_chars" arity="2"/> + <name name="put_chars" arity="1" since=""/> + <name name="put_chars" arity="2" since=""/> <fsummary>Write a list of characters.</fsummary> <desc> <p>Writes the characters of <c><anno>CharData</anno></c> to the I/O @@ -928,8 +928,8 @@ enter><input>abc("hey".</input> </func> <func> - <name name="read" arity="1"/> - <name name="read" arity="2"/> + <name name="read" arity="1" since=""/> + <name name="read" arity="2" since=""/> <fsummary>Read a term.</fsummary> <type name="server_no_data"/> <desc> @@ -960,8 +960,8 @@ enter><input>abc("hey".</input> </func> <func> - <name name="read" arity="3"/> - <name name="read" arity="4"/> + <name name="read" arity="3" since=""/> + <name name="read" arity="4" since="OTP R16B"/> <fsummary>Read a term.</fsummary> <type name="server_no_data"/> <desc> @@ -997,8 +997,8 @@ enter><input>abc("hey".</input> </func> <func> - <name name="rows" arity="0"/> - <name name="rows" arity="1"/> + <name name="rows" arity="0" since=""/> + <name name="rows" arity="1" since=""/> <fsummary>Get the number of rows of an I/O device.</fsummary> <desc> <p>Retrieves the number of rows of <c><anno>IoDevice</anno></c> @@ -1009,10 +1009,10 @@ enter><input>abc("hey".</input> </func> <func> - <name name="scan_erl_exprs" arity="1"/> - <name name="scan_erl_exprs" arity="2"/> - <name name="scan_erl_exprs" arity="3"/> - <name name="scan_erl_exprs" arity="4"/> + <name name="scan_erl_exprs" arity="1" since=""/> + <name name="scan_erl_exprs" arity="2" since=""/> + <name name="scan_erl_exprs" arity="3" since=""/> + <name name="scan_erl_exprs" arity="4" since="OTP R16B"/> <fsummary>Read and tokenize Erlang expressions.</fsummary> <type name="server_no_data"/> <desc> @@ -1060,10 +1060,10 @@ enter><input>1.0er.</input> </func> <func> - <name name="scan_erl_form" arity="1"/> - <name name="scan_erl_form" arity="2"/> - <name name="scan_erl_form" arity="3"/> - <name name="scan_erl_form" arity="4"/> + <name name="scan_erl_form" arity="1" since=""/> + <name name="scan_erl_form" arity="2" since=""/> + <name name="scan_erl_form" arity="3" since=""/> + <name name="scan_erl_form" arity="4" since="OTP R16B"/> <fsummary>Read and tokenize an Erlang form.</fsummary> <type name="server_no_data"/> <desc> @@ -1083,8 +1083,8 @@ enter><input>1.0er.</input> </func> <func> - <name name="setopts" arity="1"/> - <name name="setopts" arity="2"/> + <name name="setopts" arity="1" since=""/> + <name name="setopts" arity="2" since=""/> <fsummary>Set options.</fsummary> <desc> <p>Set options for the standard I/O device @@ -1198,8 +1198,8 @@ fun("") -> {yes, "quit", []}; </func> <func> - <name name="write" arity="1"/> - <name name="write" arity="2"/> + <name name="write" arity="1" since=""/> + <name name="write" arity="2" since=""/> <fsummary>Write a term.</fsummary> <desc> <p>Writes term <c><anno>Term</anno></c> to the standard output diff --git a/lib/stdlib/doc/src/io_lib.xml b/lib/stdlib/doc/src/io_lib.xml index a3df2897ac..4d527f8ed3 100644 --- a/lib/stdlib/doc/src/io_lib.xml +++ b/lib/stdlib/doc/src/io_lib.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>io_lib</module> + <module since="">io_lib</module> <modulesummary>I/O library functions.</modulesummary> <description> <p>This module contains functions for converting to and from @@ -99,7 +99,7 @@ <funcs> <func> - <name name="build_text" arity="1"/> + <name name="build_text" arity="1" since="OTP 18.0"/> <fsummary>Build the output text for a preparsed format list.</fsummary> <desc> <p>For details, see @@ -108,7 +108,7 @@ </func> <func> - <name name="char_list" arity="1"/> + <name name="char_list" arity="1" since=""/> <fsummary>Test for a list of characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of @@ -117,7 +117,7 @@ </func> <func> - <name name="deep_char_list" arity="1"/> + <name name="deep_char_list" arity="1" since=""/> <fsummary>Test for a deep list of characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a, possibly deep, @@ -126,7 +126,7 @@ </func> <func> - <name name="deep_latin1_char_list" arity="1"/> + <name name="deep_latin1_char_list" arity="1" since="OTP R16B"/> <fsummary>Test for a deep list of characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a, possibly deep, @@ -136,8 +136,8 @@ </func> <func> - <name name="format" arity="2"/> - <name name="fwrite" arity="2"/> + <name name="format" arity="2" since=""/> + <name name="fwrite" arity="2" since=""/> <fsummary>Write formatted output.</fsummary> <desc> <p>Returns a character list that represents <c><anno>Data</anno></c> @@ -156,8 +156,8 @@ </func> <func> - <name name="format" arity="3"/> - <name name="fwrite" arity="3"/> + <name name="format" arity="3" since="OTP 21.0"/> + <name name="fwrite" arity="3" since="OTP 21.0"/> <fsummary>Write formatted output.</fsummary> <desc> <p>Returns a character list that represents <c><anno>Data</anno></c> @@ -181,7 +181,7 @@ </func> <func> - <name name="fread" arity="2"/> + <name name="fread" arity="2" since=""/> <fsummary>Read formatted input.</fsummary> <desc> <p>Tries to read <c><anno>String</anno></c> in accordance with the @@ -222,7 +222,7 @@ </func> <func> - <name name="fread" arity="3"/> + <name name="fread" arity="3" since=""/> <fsummary>Re-entrant formatted reader</fsummary> <desc> <p>This is the re-entrant formatted reader. The continuation of @@ -268,7 +268,7 @@ </func> <func> - <name name="indentation" arity="2"/> + <name name="indentation" arity="2" since=""/> <fsummary>Indentation after printing string.</fsummary> <desc> <p>Returns the indentation if <c><anno>String</anno></c> has been @@ -277,7 +277,7 @@ </func> <func> - <name name="latin1_char_list" arity="1"/> + <name name="latin1_char_list" arity="1" since="OTP R16B"/> <fsummary>Test for a list of ISO Latin-1 characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of @@ -286,7 +286,7 @@ </func> <func> - <name name="nl" arity="0"/> + <name name="nl" arity="0" since=""/> <fsummary>Write a newline.</fsummary> <desc> <p>Returns a character list that represents a new line character.</p> @@ -294,8 +294,8 @@ </func> <func> - <name name="print" arity="1"/> - <name name="print" arity="4"/> + <name name="print" arity="1" since=""/> + <name name="print" arity="4" since=""/> <fsummary>Pretty print a term.</fsummary> <desc> <p>Returns a list of characters that represents @@ -315,7 +315,7 @@ </func> <func> - <name name="printable_latin1_list" arity="1"/> + <name name="printable_latin1_list" arity="1" since="OTP R16B"/> <fsummary>Test for a list of printable ISO Latin-1 characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of @@ -324,7 +324,7 @@ </func> <func> - <name name="printable_list" arity="1"/> + <name name="printable_list" arity="1" since=""/> <fsummary>Test for a list of printable characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of @@ -338,7 +338,7 @@ </func> <func> - <name name="printable_unicode_list" arity="1"/> + <name name="printable_unicode_list" arity="1" since="OTP R16B"/> <fsummary>Test for a list of printable Unicode characters.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is a flat list of @@ -347,7 +347,7 @@ </func> <func> - <name name="scan_format" arity="2"/> + <name name="scan_format" arity="2" since="OTP 18.0"/> <fsummary>Parse all control sequences in the format string.</fsummary> <desc> <p>Returns a list corresponding to the specified format string, @@ -373,7 +373,7 @@ </func> <func> - <name name="unscan_format" arity="1"/> + <name name="unscan_format" arity="1" since="OTP 18.0"/> <fsummary>Revert a preparsed format list to a plain character list and a list of arguments.</fsummary> <desc> @@ -383,9 +383,9 @@ </func> <func> - <name name="write" arity="1"/> - <name name="write" arity="2" clause_i="1"/> - <name name="write" arity="2" clause_i="2"/> + <name name="write" arity="1" since=""/> + <name name="write" arity="2" clause_i="1" since=""/> + <name name="write" arity="2" clause_i="2" since="OTP 20.0"/> <fsummary>Write a term.</fsummary> <desc> <p>Returns a character list that represents <c><anno>Term</anno></c>. @@ -411,7 +411,7 @@ </func> <func> - <name name="write_atom" arity="1"/> + <name name="write_atom" arity="1" since=""/> <fsummary>Write an atom.</fsummary> <desc> <p>Returns the list of characters needed to print atom @@ -420,7 +420,7 @@ </func> <func> - <name name="write_atom_as_latin1" arity="1"/> + <name name="write_atom_as_latin1" arity="1" since="OTP 20.0"/> <fsummary>Write an atom.</fsummary> <desc> <p>Returns the list of characters needed to print atom @@ -430,7 +430,7 @@ </func> <func> - <name name="write_char" arity="1"/> + <name name="write_char" arity="1" since=""/> <fsummary>Write a character.</fsummary> <desc> <p>Returns the list of characters needed to print a character @@ -439,7 +439,7 @@ </func> <func> - <name name="write_char_as_latin1" arity="1"/> + <name name="write_char_as_latin1" arity="1" since="OTP R16B"/> <fsummary>Write a character.</fsummary> <desc> <p>Returns the list of characters needed to print a character @@ -449,7 +449,7 @@ </func> <func> - <name name="write_latin1_char" arity="1"/> + <name name="write_latin1_char" arity="1" since="OTP R16B"/> <fsummary>Write an ISO Latin-1 character.</fsummary> <desc> <p>Returns the list of characters needed to print a character @@ -458,7 +458,7 @@ </func> <func> - <name name="write_latin1_string" arity="1"/> + <name name="write_latin1_string" arity="1" since="OTP R16B"/> <fsummary>Write an ISO Latin-1 string.</fsummary> <desc> <p>Returns the list of characters needed to print @@ -467,7 +467,7 @@ </func> <func> - <name name="write_string" arity="1"/> + <name name="write_string" arity="1" since=""/> <fsummary>Write a string.</fsummary> <desc> <p>Returns the list of characters needed to print @@ -476,7 +476,7 @@ </func> <func> - <name name="write_string_as_latin1" arity="1"/> + <name name="write_string_as_latin1" arity="1" since="OTP R16B"/> <fsummary>Write a string.</fsummary> <desc> <p>Returns the list of characters needed to print diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml index e4215a5336..2755fb3dce 100644 --- a/lib/stdlib/doc/src/lists.xml +++ b/lib/stdlib/doc/src/lists.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2018</year> + <year>1996</year><year>2019</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -28,7 +28,7 @@ <date>1996-09-28</date> <rev>A</rev> </header> - <module>lists</module> + <module since="">lists</module> <modulesummary>List processing functions.</modulesummary> <description> <p>This module contains functions for list processing.</p> @@ -63,7 +63,7 @@ <funcs> <func> - <name name="all" arity="2"/> + <name name="all" arity="2" since=""/> <fsummary>Return <c>true</c> if all elements in a list satisfy <c>Pred</c>.</fsummary> <desc> @@ -74,7 +74,7 @@ </func> <func> - <name name="any" arity="2"/> + <name name="any" arity="2" since=""/> <fsummary>Return <c>true</c> if any of the elements in a list satisfies <c>Pred</c>.</fsummary> <desc> @@ -85,7 +85,7 @@ </func> <func> - <name name="append" arity="1"/> + <name name="append" arity="1" since=""/> <fsummary>Append a list of lists.</fsummary> <desc> <p>Returns a list in which all the sublists of @@ -98,7 +98,7 @@ </func> <func> - <name name="append" arity="2"/> + <name name="append" arity="2" since=""/> <fsummary>Append two lists.</fsummary> <desc> <p>Returns a new list <c><anno>List3</anno></c>, which is made from @@ -113,7 +113,7 @@ </func> <func> - <name name="concat" arity="1"/> + <name name="concat" arity="1" since=""/> <fsummary>Concatenate a list of atoms.</fsummary> <desc> <p>Concatenates the text representation of the elements of @@ -127,7 +127,7 @@ </func> <func> - <name name="delete" arity="2"/> + <name name="delete" arity="2" since=""/> <fsummary>Delete an element from a list.</fsummary> <desc> <p>Returns a copy of <c><anno>List1</anno></c> where the first element @@ -137,7 +137,7 @@ </func> <func> - <name name="droplast" arity="1"/> + <name name="droplast" arity="1" since="OTP 17.0"/> <fsummary>Drop the last element of a list.</fsummary> <desc> <p>Drops the last element of a <c><anno>List</anno></c>. The list is to @@ -147,7 +147,7 @@ </func> <func> - <name name="dropwhile" arity="2"/> + <name name="dropwhile" arity="2" since=""/> <fsummary>Drop elements from a list while a predicate is <c>true</c>. </fsummary> <desc> @@ -159,7 +159,7 @@ </func> <func> - <name name="duplicate" arity="2"/> + <name name="duplicate" arity="2" since=""/> <fsummary>Make <c>N</c> copies of element.</fsummary> <desc> <p>Returns a list containing <c><anno>N</anno></c> copies of term @@ -172,7 +172,7 @@ </func> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Select elements that satisfy a predicate.</fsummary> <desc> <p><c><anno>List2</anno></c> is a list of all elements @@ -182,7 +182,7 @@ </func> <func> - <name name="filtermap" arity="2"/> + <name name="filtermap" arity="2" since="OTP R16B01"/> <fsummary>Filter and map elements that satisfy a function.</fsummary> <desc> <p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> on successive @@ -211,7 +211,7 @@ filtermap(Fun, List1) -> </func> <func> - <name name="flatlength" arity="1"/> + <name name="flatlength" arity="1" since=""/> <fsummary>Length of flattened deep list.</fsummary> <desc> <p>Equivalent to <c>length(flatten(<anno>DeepList</anno>))</c>, but @@ -220,7 +220,7 @@ filtermap(Fun, List1) -> </func> <func> - <name name="flatmap" arity="2"/> + <name name="flatmap" arity="2" since=""/> <fsummary>Map and flatten in one pass.</fsummary> <desc> <p>Takes a function from <c><anno>A</anno></c>s to lists of @@ -241,7 +241,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="flatten" arity="1"/> + <name name="flatten" arity="1" since=""/> <fsummary>Flatten a deep list.</fsummary> <desc> <p>Returns a flattened version of <c><anno>DeepList</anno></c>.</p> @@ -249,7 +249,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="flatten" arity="2"/> + <name name="flatten" arity="2" since=""/> <fsummary>Flatten a deep list.</fsummary> <desc> <p>Returns a flattened version of <c><anno>DeepList</anno></c> with tail @@ -258,7 +258,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="foldl" arity="3"/> + <name name="foldl" arity="3" since=""/> <fsummary>Fold a function over a list.</fsummary> <desc> <p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>, <anno>AccIn</anno>)</c> @@ -278,7 +278,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="foldr" arity="3"/> + <name name="foldr" arity="3" since=""/> <fsummary>Fold a function over a list.</fsummary> <desc> <p>Like <seealso marker="#foldl/3"><c>foldl/3</c></seealso>, but the @@ -297,7 +297,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="join" arity="2"/> + <name name="join" arity="2" since="OTP 19.0"/> <fsummary>Insert an element between elements in a list</fsummary> <desc> <p>Inserts <c><anno>Sep</anno></c> between each element in <c><anno>List1</anno></c>. Has no @@ -312,7 +312,7 @@ flatmap(Fun, List1) -> </desc> </func> <func> - <name name="foreach" arity="2"/> + <name name="foreach" arity="2" since=""/> <fsummary>Apply a function to each element of a list.</fsummary> <desc> <p>Calls <c><anno>Fun</anno>(<anno>Elem</anno>)</c> for each element @@ -324,7 +324,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keydelete" arity="3"/> + <name name="keydelete" arity="3" since=""/> <fsummary>Delete an element from a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -336,7 +336,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keyfind" arity="3"/> + <name name="keyfind" arity="3" since=""/> <fsummary>Search for an element in a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -349,7 +349,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keymap" arity="3"/> + <name name="keymap" arity="3" since=""/> <fsummary>Map a function over a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -368,7 +368,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keymember" arity="3"/> + <name name="keymember" arity="3" since=""/> <fsummary>Test for membership of a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -379,7 +379,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keymerge" arity="3"/> + <name name="keymerge" arity="3" since=""/> <fsummary>Merge two key-sorted lists of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -395,7 +395,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keyreplace" arity="4"/> + <name name="keyreplace" arity="4" since=""/> <fsummary>Replace an element in a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -407,7 +407,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keysearch" arity="3"/> + <name name="keysearch" arity="3" since=""/> <fsummary>Search for an element in a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -425,7 +425,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keysort" arity="2"/> + <name name="keysort" arity="2" since=""/> <fsummary>Sort a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -436,7 +436,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keystore" arity="4"/> + <name name="keystore" arity="4" since=""/> <fsummary>Store an element in a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -452,7 +452,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="keytake" arity="3"/> + <name name="keytake" arity="3" since=""/> <fsummary>Extract an element from a list of tuples.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -467,7 +467,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="last" arity="1"/> + <name name="last" arity="1" since=""/> <fsummary>Return last element in a list.</fsummary> <desc> <p>Returns the last element in <c><anno>List</anno></c>.</p> @@ -475,7 +475,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="map" arity="2"/> + <name name="map" arity="2" since=""/> <fsummary>Map a function over a list.</fsummary> <desc> <p>Takes a function from <c><anno>A</anno></c>s to @@ -488,7 +488,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="mapfoldl" arity="3"/> + <name name="mapfoldl" arity="3" since=""/> <fsummary>Map and fold in one pass.</fsummary> <desc> <p>Combines the operations of @@ -504,7 +504,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="mapfoldr" arity="3"/> + <name name="mapfoldr" arity="3" since=""/> <fsummary>Map and fold in one pass.</fsummary> <desc> <p>Combines the operations of @@ -514,7 +514,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="max" arity="1"/> + <name name="max" arity="1" since=""/> <fsummary>Return maximum element of a list.</fsummary> <desc> <p>Returns the first element of <c><anno>List</anno></c> that compares @@ -524,7 +524,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="member" arity="2"/> + <name name="member" arity="2" since=""/> <fsummary>Test for membership of a list.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Elem</anno></c> matches some element @@ -533,7 +533,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="merge" arity="1"/> + <name name="merge" arity="1" since=""/> <fsummary>Merge a list of sorted lists.</fsummary> <desc> <p>Returns the sorted list formed by merging all the sublists of @@ -546,7 +546,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="merge" arity="2"/> + <name name="merge" arity="2" since=""/> <fsummary>Merge two sorted lists.</fsummary> <desc> <p>Returns the sorted list formed by merging <c><anno>List1</anno></c> @@ -559,7 +559,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="merge" arity="3"/> + <name name="merge" arity="3" since=""/> <fsummary>Merge two sorted list.</fsummary> <desc> <p>Returns the sorted list formed by merging <c><anno>List1</anno></c> @@ -577,7 +577,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="merge3" arity="3"/> + <name name="merge3" arity="3" since=""/> <fsummary>Merge three sorted lists.</fsummary> <desc> <p>Returns the sorted list formed by merging <c><anno>List1</anno></c>, @@ -593,7 +593,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="min" arity="1"/> + <name name="min" arity="1" since=""/> <fsummary>Return minimum element of a list.</fsummary> <desc> <p>Returns the first element of <c><anno>List</anno></c> that compares @@ -603,7 +603,7 @@ flatmap(Fun, List1) -> </func> <func> - <name name="nth" arity="2"/> + <name name="nth" arity="2" since=""/> <fsummary>Return the <c>N</c>th element of a list.</fsummary> <type_desc variable="N">1..length(<anno>List</anno>)</type_desc> <desc> @@ -617,7 +617,7 @@ c</pre> </func> <func> - <name name="nthtail" arity="2"/> + <name name="nthtail" arity="2" since=""/> <fsummary>Return the <c>N</c>th tail of a list.</fsummary> <type_desc variable="N">0..length(<anno>List</anno>)</type_desc> <desc> @@ -638,7 +638,7 @@ c</pre> </func> <func> - <name name="partition" arity="2"/> + <name name="partition" arity="2" since=""/> <fsummary>Partition a list into two lists based on a predicate.</fsummary> <desc> <p>Partitions <c><anno>List</anno></c> into two lists, where the first @@ -658,7 +658,7 @@ c</pre> </func> <func> - <name name="prefix" arity="2"/> + <name name="prefix" arity="2" since=""/> <fsummary>Test for list prefix.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>List1</anno></c> is a prefix of @@ -667,7 +667,7 @@ c</pre> </func> <func> - <name name="reverse" arity="1"/> + <name name="reverse" arity="1" since=""/> <fsummary>Reverse a list.</fsummary> <desc> <p>Returns a list with the elements in <c><anno>List1</anno></c> @@ -676,7 +676,7 @@ c</pre> </func> <func> - <name name="reverse" arity="2"/> + <name name="reverse" arity="2" since=""/> <fsummary>Reverse a list appending a tail.</fsummary> <desc> <p>Returns a list with the elements in <c><anno>List1</anno></c> @@ -689,8 +689,20 @@ c</pre> </func> <func> - <name name="seq" arity="2"/> - <name name="seq" arity="3"/> + <name name="search" arity="2" since="OTP 21.0"/> + <fsummary>Find the first element that satisfies a predicate.</fsummary> + <desc> + <p>If there is a <c><anno>Value</anno></c> in <c><anno>List</anno></c> + such that <c><anno>Pred</anno>(<anno>Value</anno>)</c> returns + <c>true</c>, returns <c>{value, <anno>Value</anno>}</c> + for the first such <c><anno>Value</anno></c>, + otherwise returns <c>false</c>.</p> + </desc> + </func> + + <func> + <name name="seq" arity="2" since=""/> + <name name="seq" arity="3" since=""/> <fsummary>Generate a sequence of integers.</fsummary> <desc> <p>Returns a sequence of integers that starts with @@ -736,7 +748,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code> </func> <func> - <name name="sort" arity="1"/> + <name name="sort" arity="1" since=""/> <fsummary>Sort a list.</fsummary> <desc> <p>Returns a list containing the sorted elements of @@ -745,7 +757,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code> </func> <func> - <name name="sort" arity="2"/> + <name name="sort" arity="2" since=""/> <fsummary>Sort a list.</fsummary> <desc> <p>Returns a list containing the sorted elements of @@ -759,7 +771,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code> </func> <func> - <name name="split" arity="2"/> + <name name="split" arity="2" since=""/> <fsummary>Split a list into two lists.</fsummary> <type_desc variable="N">0..length(<anno>List1</anno>)</type_desc> <desc> @@ -771,19 +783,7 @@ length(lists:seq(From, To, Incr)) =:= (To - From + Incr) div Incr</code> </func> <func> - <name name="search" arity="2"/> - <fsummary>Find the first element that satisfies a predicate.</fsummary> - <desc> - <p>If there is a <c><anno>Value</anno></c> in <c><anno>List</anno></c> - such that <c><anno>Pred</anno>(<anno>Value</anno>)</c> returns - <c>true</c>, returns <c>{value, <anno>Value</anno>}</c> - for the first such <c><anno>Value</anno></c>, - otherwise returns <c>false</c>.</p> - </desc> - </func> - - <func> - <name name="splitwith" arity="2"/> + <name name="splitwith" arity="2" since=""/> <fsummary>Split a list into two lists based on a predicate.</fsummary> <desc> <p>Partitions <c><anno>List</anno></c> into two lists according to @@ -804,7 +804,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="sublist" arity="2"/> + <name name="sublist" arity="2" since=""/> <fsummary>Return a sublist of a certain length, starting at the first position.</fsummary> <desc> @@ -816,7 +816,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="sublist" arity="3"/> + <name name="sublist" arity="3" since=""/> <fsummary>Return a sublist starting at a specified position and with a specified number of elements.</fsummary> <type_desc variable="Start">1..(length(<anno>List1</anno>)+1)</type_desc> @@ -838,7 +838,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="subtract" arity="2"/> + <name name="subtract" arity="2" since=""/> <fsummary>Subtract the element in one list from another list.</fsummary> <desc> <p>Returns a new list <c><anno>List3</anno></c> that is a copy of @@ -854,7 +854,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="suffix" arity="2"/> + <name name="suffix" arity="2" since=""/> <fsummary>Test for list suffix.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>List1</anno></c> is a suffix of @@ -863,7 +863,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="sum" arity="1"/> + <name name="sum" arity="1" since=""/> <fsummary>Return the sum of elements in a list.</fsummary> <desc> <p>Returns the sum of the elements in <c><anno>List</anno></c>.</p> @@ -871,7 +871,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="takewhile" arity="2"/> + <name name="takewhile" arity="2" since=""/> <fsummary>Take elements from a list while a predicate is <c>true</c>. </fsummary> <desc> @@ -884,7 +884,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="ukeymerge" arity="3"/> + <name name="ukeymerge" arity="3" since=""/> <fsummary>Merge two key-sorted lists of tuples, removing duplicates. </fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> @@ -902,7 +902,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="ukeysort" arity="2"/> + <name name="ukeysort" arity="2" since=""/> <fsummary>Sort a list of tuples, removing duplicates.</fsummary> <type_desc variable="N">1..tuple_size(<anno>Tuple</anno>)</type_desc> <desc> @@ -914,7 +914,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="umerge" arity="1"/> + <name name="umerge" arity="1" since=""/> <fsummary>Merge a list of sorted lists, removing duplicates.</fsummary> <desc> <p>Returns the sorted list formed by merging all the sublists @@ -927,7 +927,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="umerge" arity="2"/> + <name name="umerge" arity="2" since=""/> <fsummary>Merge two sorted lists, removing duplicates.</fsummary> <desc> <p>Returns the sorted list formed by merging <c><anno>List1</anno></c> @@ -941,7 +941,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="umerge" arity="3"/> + <name name="umerge" arity="3" since=""/> <fsummary>Merge two sorted lists, removing duplicates.</fsummary> <desc> <p>Returns the sorted list formed by merging <c><anno>List1</anno></c> @@ -958,7 +958,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="umerge3" arity="3"/> + <name name="umerge3" arity="3" since=""/> <fsummary>Merge three sorted lists, removing duplicates.</fsummary> <desc> <p>Returns the sorted list formed by merging <c><anno>List1</anno></c>, @@ -973,7 +973,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="unzip" arity="1"/> + <name name="unzip" arity="1" since=""/> <fsummary>Unzip a list of two-tuples into two lists.</fsummary> <desc> <p>"Unzips" a list of two-tuples into two lists, where the first @@ -983,7 +983,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="unzip3" arity="1"/> + <name name="unzip3" arity="1" since=""/> <fsummary>Unzip a list of three-tuples into three lists.</fsummary> <desc> <p>"Unzips" a list of three-tuples into three lists, where @@ -994,7 +994,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="usort" arity="1"/> + <name name="usort" arity="1" since=""/> <fsummary>Sort a list, removing duplicates.</fsummary> <desc> <p>Returns a list containing the sorted elements of @@ -1004,7 +1004,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="usort" arity="2"/> + <name name="usort" arity="2" since=""/> <fsummary>Sort a list, removing duplicates.</fsummary> <desc> <p>Returns a list containing the sorted elements of @@ -1019,7 +1019,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="zip" arity="2"/> + <name name="zip" arity="2" since=""/> <fsummary>Zip two lists into a list of two-tuples.</fsummary> <desc> <p>"Zips" two lists of equal length into one list of two-tuples, @@ -1030,7 +1030,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="zip3" arity="3"/> + <name name="zip3" arity="3" since=""/> <fsummary>Zip three lists into a list of three-tuples.</fsummary> <desc> <p>"Zips" three lists of equal length into one list of @@ -1042,7 +1042,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="zipwith" arity="3"/> + <name name="zipwith" arity="3" since=""/> <fsummary>Zip two lists into one list according to a fun.</fsummary> <desc> <p>Combines the elements of two lists of equal length into one list. @@ -1059,7 +1059,7 @@ splitwith(Pred, List) -> </func> <func> - <name name="zipwith3" arity="4"/> + <name name="zipwith3" arity="4" since=""/> <fsummary>Zip three lists into one list according to a fun.</fsummary> <desc> <p>Combines the elements of three lists of equal length into one diff --git a/lib/stdlib/doc/src/log_mf_h.xml b/lib/stdlib/doc/src/log_mf_h.xml index edc3d31025..b922006cc0 100644 --- a/lib/stdlib/doc/src/log_mf_h.xml +++ b/lib/stdlib/doc/src/log_mf_h.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>log_mf_h.xml</file> </header> - <module>log_mf_h</module> + <module since="">log_mf_h</module> <modulesummary>An event handler that logs events to disk.</modulesummary> <description> <p>This module is a <c>gen_event</c> handler module that can be installed @@ -60,8 +60,8 @@ <funcs> <func> - <name name="init" arity="3"/> - <name name="init" arity="4"/> + <name name="init" arity="3" since=""/> + <name name="init" arity="4" since=""/> <fsummary>Initiate the event handler.</fsummary> <desc> <p>Initiates the event handler. Returns <c><anno>Args</anno></c>, which diff --git a/lib/stdlib/doc/src/maps.xml b/lib/stdlib/doc/src/maps.xml index 4c5199ca2b..8e88882b56 100644 --- a/lib/stdlib/doc/src/maps.xml +++ b/lib/stdlib/doc/src/maps.xml @@ -27,7 +27,7 @@ <date>2014-02-28</date> <rev>A</rev> </header> - <module>maps</module> + <module since="OTP 17.0">maps</module> <modulesummary>Maps processing functions.</modulesummary> <description> <p>This module contains functions for maps processing.</p> @@ -54,7 +54,7 @@ <funcs> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since="OTP 18.0"/> <fsummary>Select pairs that satisfy a predicate.</fsummary> <desc> <p>Returns a map <c><anno>Map</anno></c> for which predicate @@ -73,7 +73,7 @@ </func> <func> - <name name="find" arity="2"/> + <name name="find" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns a tuple <c>{ok, Value}</c>, where <c><anno>Value</anno></c> @@ -92,7 +92,7 @@ </func> <func> - <name name="fold" arity="3"/> + <name name="fold" arity="3" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Calls <c>F(Key, Value, AccIn)</c> for every <c><anno>Key</anno></c> @@ -116,7 +116,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Takes a list of key-value tuples elements and builds a map. The @@ -133,7 +133,7 @@ </func> <func> - <name name="get" arity="2"/> + <name name="get" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns value <c><anno>Value</anno></c> associated with @@ -152,7 +152,7 @@ </func> <func> - <name name="get" arity="3"/> + <name name="get" arity="3" since="OTP 17.1"/> <fsummary></fsummary> <desc> <p>Returns value <c><anno>Value</anno></c> associated with @@ -173,7 +173,7 @@ val1 </func> <func> - <name name="is_key" arity="2"/> + <name name="is_key" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns <c>true</c> if map <c><anno>Map</anno></c> contains @@ -193,7 +193,7 @@ false</code> </func> <func> - <name name="iterator" arity="1"/> + <name name="iterator" arity="1" since="OTP 21.0"/> <fsummary>Create a map iterator.</fsummary> <desc> <p>Returns a map iterator <c><anno>Iterator</anno></c> that can @@ -207,19 +207,19 @@ false</code> <code type="none"> > M = #{ a => 1, b => 2 }. #{a => 1,b => 2} -> I = maps:iterator(M). -[{a,1},{b,2}] -> {K1, V1, I2} = maps:next(I). -{a,1,[{b,2}]} -> {K2, V2, I3} = maps:next(I2). -{b,2,[]} +> I = maps:iterator(M), ok. +ok +> {K1, V1, I2} = maps:next(I), {K1, V1}. +{a,1} +> {K2, V2, I3} = maps:next(I2),{K2, V2}. +{b,2} > maps:next(I3). none</code> </desc> </func> <func> - <name name="keys" arity="1"/> + <name name="keys" arity="1" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns a complete list of keys, in any order, which resides @@ -235,7 +235,7 @@ none</code> </func> <func> - <name name="map" arity="2"/> + <name name="map" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Produces a new map <c><anno>Map</anno></c> by calling function @@ -259,7 +259,7 @@ none</code> </func> <func> - <name name="merge" arity="2"/> + <name name="merge" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Merges two maps into a single map <c><anno>Map3</anno></c>. If two @@ -277,7 +277,7 @@ none</code> </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns a new empty map.</p> @@ -289,7 +289,7 @@ none</code> </func> <func> - <name name="next" arity="1"/> + <name name="next" arity="1" since="OTP 21.0"/> <fsummary>Get the next key and value from an iterator.</fsummary> <desc> <p>Returns the next key-value association in @@ -304,21 +304,21 @@ none</code> <code type="none"> > Map = #{a => 1, b => 2, c => 3}. #{a => 1,b => 2,c => 3} -> Iter = maps:iterator(Map). -[{a,1},{b,2},{c,3}] -> {_, _, Iter1} = maps:next(Iter). -{a,1,[{b,2},{c,3}]} -> {_, _, Iter2} = maps:next(Iter1). -{b,2,[{c,3}]} -> {_, _, Iter3} = maps:next(Iter2). -{c,3,[]} -> maps:next(Iter3). +> I = maps:iterator(Map), ok. +ok +> {K1, V1, I1} = maps:next(I), {K1, V1}. +{a,1} +> {K2, V2, I2} = maps:next(I1), {K2, V2}. +{b,2} +> {K3, V3, I3} = maps:next(I2), {K3, V3}. +{c,3} +> maps:next(I3). none</code> </desc> </func> <func> - <name name="put" arity="3"/> + <name name="put" arity="3" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Associates <c><anno>Key</anno></c> with value @@ -342,7 +342,7 @@ none</code> </func> <func> - <name name="remove" arity="2"/> + <name name="remove" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Removes the <c><anno>Key</anno></c>, if it exists, and its @@ -362,7 +362,7 @@ none</code> </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns the number of key-value associations in @@ -376,7 +376,7 @@ none</code> </func> <func> - <name name="take" arity="2"/> + <name name="take" arity="2" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>The function removes the <c><anno>Key</anno></c>, if it @@ -401,7 +401,7 @@ error</code> </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns a list of pairs representing the key-value associations of @@ -418,7 +418,7 @@ error</code> </func> <func> - <name name="update" arity="3"/> + <name name="update" arity="3" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>If <c><anno>Key</anno></c> exists in <c><anno>Map1</anno></c>, the @@ -438,7 +438,7 @@ error</code> </func> <func> - <name name="update_with" arity="3"/> + <name name="update_with" arity="3" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Update a value in a <c><anno>Map1</anno></c> associated @@ -457,7 +457,7 @@ error</code> </func> <func> - <name name="update_with" arity="4"/> + <name name="update_with" arity="4" since="OTP 19.0"/> <fsummary></fsummary> <desc> <p>Update a value in a <c><anno>Map1</anno></c> associated @@ -477,7 +477,7 @@ error</code> </func> <func> - <name name="values" arity="1"/> + <name name="values" arity="1" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns a complete list of values, in arbitrary order, contained in @@ -493,7 +493,7 @@ error</code> </func> <func> - <name name="with" arity="2"/> + <name name="with" arity="2" since="OTP 17.3"/> <fsummary></fsummary> <desc> <p>Returns a new map <c><anno>Map2</anno></c> with the keys <c>K1</c> @@ -510,7 +510,7 @@ error</code> </func> <func> - <name name="without" arity="2"/> + <name name="without" arity="2" since="OTP 17.0"/> <fsummary></fsummary> <desc> <p>Returns a new map <c><anno>Map2</anno></c> without keys <c>K1</c> diff --git a/lib/stdlib/doc/src/math.xml b/lib/stdlib/doc/src/math.xml index b4f096217a..d89310e2c8 100644 --- a/lib/stdlib/doc/src/math.xml +++ b/lib/stdlib/doc/src/math.xml @@ -34,7 +34,7 @@ <rev>B</rev> <file>math.xml</file> </header> - <module>math</module> + <module since="">math</module> <modulesummary>Mathematical functions.</modulesummary> <description> <p>This module provides an interface to a number of mathematical @@ -50,28 +50,28 @@ <funcs> <func> - <name name="acos" arity="1"/> - <name name="acosh" arity="1"/> - <name name="asin" arity="1"/> - <name name="asinh" arity="1"/> - <name name="atan" arity="1"/> - <name name="atan2" arity="2"/> - <name name="atanh" arity="1"/> - <name name="ceil" arity="1"/> - <name name="cos" arity="1"/> - <name name="cosh" arity="1"/> - <name name="exp" arity="1"/> - <name name="floor" arity="1"/> - <name name="fmod" arity="2"/> - <name name="log" arity="1"/> - <name name="log10" arity="1"/> - <name name="log2" arity="1"/> - <name name="pow" arity="2"/> - <name name="sin" arity="1"/> - <name name="sinh" arity="1"/> - <name name="sqrt" arity="1"/> - <name name="tan" arity="1"/> - <name name="tanh" arity="1"/> + <name name="acos" arity="1" since=""/> + <name name="acosh" arity="1" since=""/> + <name name="asin" arity="1" since=""/> + <name name="asinh" arity="1" since=""/> + <name name="atan" arity="1" since=""/> + <name name="atan2" arity="2" since=""/> + <name name="atanh" arity="1" since=""/> + <name name="ceil" arity="1" since="OTP 20.0"/> + <name name="cos" arity="1" since=""/> + <name name="cosh" arity="1" since=""/> + <name name="exp" arity="1" since=""/> + <name name="floor" arity="1" since="OTP 20.0"/> + <name name="fmod" arity="2" since="OTP 20.0"/> + <name name="log" arity="1" since=""/> + <name name="log10" arity="1" since=""/> + <name name="log2" arity="1" since="OTP 18.0"/> + <name name="pow" arity="2" since=""/> + <name name="sin" arity="1" since=""/> + <name name="sinh" arity="1" since=""/> + <name name="sqrt" arity="1" since=""/> + <name name="tan" arity="1" since=""/> + <name name="tanh" arity="1" since=""/> <fsummary>Diverse math functions.</fsummary> <type variable="X" name_i="6"/> <type variable="Y" name_i="6"/> @@ -82,7 +82,7 @@ </func> <func> - <name name="erf" arity="1"/> + <name name="erf" arity="1" since=""/> <fsummary>Error function.</fsummary> <desc> <p>Returns the error function of <c><anno>X</anno></c>, where:</p> @@ -92,7 +92,7 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre> </func> <func> - <name name="erfc" arity="1"/> + <name name="erfc" arity="1" since=""/> <fsummary>Another error function.</fsummary> <desc> <p><c>erfc(X)</c> returns <c>1.0</c> - <c>erf(X)</c>, computed by @@ -101,7 +101,7 @@ erf(X) = 2/sqrt(pi)*integral from 0 to X of exp(-t*t) dt.</pre> </func> <func> - <name name="pi" arity="0"/> + <name name="pi" arity="0" since=""/> <fsummary>A useful number.</fsummary> <desc> <p>A useful number.</p> diff --git a/lib/stdlib/doc/src/ms_transform.xml b/lib/stdlib/doc/src/ms_transform.xml index 0a05fa37c5..65cc150507 100644 --- a/lib/stdlib/doc/src/ms_transform.xml +++ b/lib/stdlib/doc/src/ms_transform.xml @@ -32,7 +32,7 @@ <rev>C</rev> <file>ms_transform.xml</file> </header> - <module>ms_transform</module> + <module since="">ms_transform</module> <modulesummary>A parse transformation that translates fun syntax into match specifications.</modulesummary> <description> @@ -731,7 +731,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code> <funcs> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Error formatting function as required by the parse transformation interface.</fsummary> <desc> <p>Takes an error code returned by one of the other functions @@ -741,7 +741,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code> </func> <func> - <name name="parse_transform" arity="2"/> + <name name="parse_transform" arity="2" since=""/> <fsummary>Transforms Erlang abstract format containing calls to ets/dbg:fun2ms/1 into literal match specifications.</fsummary> <type_desc variable="Options">Option list, required but not used. @@ -762,7 +762,7 @@ ets:select(Table, [{{'$1',test,'$2'},[],['$_']}]).</code> </func> <func> - <name name="transform_from_shell" arity="3"/> + <name name="transform_from_shell" arity="3" since=""/> <fsummary>Used when transforming funs created in the shell into match_specifications.</fsummary> <type_desc variable="BoundEnvironment">List of variable bindings in the diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index 64b97fad7f..a682811c6d 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -31,6 +31,85 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 3.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Document <c>bit_size</c> in match specifications and + allow it in <c>ets:fun2ms</c>.</p> + <p> + Own Id: OTP-15343 Aux Id: PR-1962 </p> + </item> + <item> + <p>The <c>beam()</c> type in <c>beam_lib</c> is defined + as <c>module() | file:filename() | binary()</c>. The + <c>module()</c> is misleading. Giving the module name as + an atom will only work if the BEAM file is in a current + directory.</p> + <p>To avoid confusion, <c>module()</c> has been removed + from the type. That means that there will be a Dialyzer + warning for code that call <c>beam_lib</c> with an atom + as filename, but the calls will still work.</p> + <p> + Own Id: OTP-15378 Aux Id: ERL-696 </p> + </item> + <item> + <p> + <c>unicode_util</c> crashed on certain emoji grapheme + clusters in binary strings.</p> + <p> + Own Id: OTP-15428 Aux Id: ERL-777 </p> + </item> + <item> + <p>When an external fun was used, warnings for unused + variables could be suppressed.</p> + <p> + Own Id: OTP-15437 Aux Id: ERL-762 </p> + </item> + <item> + <p> + Fix reduction count in lists:member/2</p> + <p> + Own Id: OTP-15474 Aux Id: ERIERL-229 </p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p>When specified, the <c>+{source,Name}</c> option will + now override the actual file name in stack traces, + instead of only affecting the return value of + <c>Mod:module_info()</c>.</p> + <p>The <c>+deterministic</c> flag will also affect stack + traces now, omitting all path information except the file + name, fixing a long-standing issue where deterministic + builds required deterministic paths.</p> + <p> + Own Id: OTP-15245 Aux Id: ERL-706 </p> + </item> + <item> + <p>List subtraction (The <c>--</c> operator) will now + yield properly on large inputs.</p> + <p> + Own Id: OTP-15371</p> + </item> + <item> + <p> + <c>calendar:system_time_to_rfc3339/1,2</c> no longer + remove trailing zeros from fractions.</p> + <p> + Own Id: OTP-15464</p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.6</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/doc/src/orddict.xml b/lib/stdlib/doc/src/orddict.xml index 26bbf499c6..27ccccee7e 100644 --- a/lib/stdlib/doc/src/orddict.xml +++ b/lib/stdlib/doc/src/orddict.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>orddict.xml</file> </header> - <module>orddict</module> + <module since="">orddict</module> <modulesummary>Key-value dictionary as ordered list.</modulesummary> <description> <p>This module provides a <c>Key</c>-<c>Value</c> dictionary. @@ -61,7 +61,7 @@ <funcs> <func> - <name name="append" arity="3"/> + <name name="append" arity="3" since=""/> <fsummary>Append a value to keys in a dictionary.</fsummary> <desc> <p>Appends a new <c><anno>Value</anno></c> to the current list @@ -73,7 +73,7 @@ </func> <func> - <name name="append_list" arity="3"/> + <name name="append_list" arity="3" since=""/> <fsummary>Append new values to keys in a dictionary.</fsummary> <desc> <p>Appends a list of values <c><anno>ValList</anno></c> to @@ -85,7 +85,7 @@ </func> <func> - <name name="erase" arity="2"/> + <name name="erase" arity="2" since=""/> <fsummary>Erase a key from a dictionary.</fsummary> <desc> <p>Erases all items with a specified key from a dictionary.</p> @@ -93,7 +93,7 @@ </func> <func> - <name name="fetch" arity="2"/> + <name name="fetch" arity="2" since=""/> <fsummary>Look up values in a dictionary.</fsummary> <desc> <p>Returns the value associated with <c><anno>Key</anno></c> @@ -105,7 +105,7 @@ </func> <func> - <name name="fetch_keys" arity="1"/> + <name name="fetch_keys" arity="1" since=""/> <fsummary>Return all keys in a dictionary.</fsummary> <desc> <p>Returns a list of all keys in a dictionary.</p> @@ -113,7 +113,7 @@ </func> <func> - <name name="take" arity="2"/> + <name name="take" arity="2" since="OTP 20.0"/> <fsummary>Return value and new dictionary without element with this value.</fsummary> <desc> <p>This function returns value from dictionary and new dictionary without this value. @@ -122,7 +122,7 @@ </func> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Select elements that satisfy a predicate.</fsummary> <desc> <p><c><anno>Orddict2</anno></c> is a dictionary of all keys and values @@ -133,7 +133,7 @@ </func> <func> - <name name="find" arity="2"/> + <name name="find" arity="2" since=""/> <fsummary>Search for a key in a dictionary.</fsummary> <desc> <p>Searches for a key in a dictionary. Returns @@ -145,7 +145,7 @@ </func> <func> - <name name="fold" arity="3"/> + <name name="fold" arity="3" since=""/> <fsummary>Fold a function over a dictionary.</fsummary> <desc> <p>Calls <c><anno>Fun</anno></c> on successive keys and values of @@ -157,7 +157,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Convert a list of pairs to a dictionary.</fsummary> <desc> <p>Converts the <c><anno>Key</anno></c>-<c><anno>Value</anno></c> list @@ -166,7 +166,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since="OTP 17.0"/> <fsummary>Return true if the dictionary is empty.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Orddict</anno></c> has no elements, @@ -175,7 +175,7 @@ </func> <func> - <name name="is_key" arity="2"/> + <name name="is_key" arity="2" since=""/> <fsummary>Test if a key is in a dictionary.</fsummary> <desc> <p>Tests if <c><anno>Key</anno></c> is contained in @@ -184,7 +184,7 @@ </func> <func> - <name name="map" arity="2"/> + <name name="map" arity="2" since=""/> <fsummary>Map a function over a dictionary.</fsummary> <desc> <p>Calls <c><anno>Fun</anno></c> on successive keys and values of @@ -193,7 +193,7 @@ </func> <func> - <name name="merge" arity="3"/> + <name name="merge" arity="3" since=""/> <fsummary>Merge two dictionaries.</fsummary> <desc> <p>Merges two dictionaries, <c><anno>Orddict1</anno></c> and @@ -212,7 +212,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Create a dictionary.</fsummary> <desc> <p>Creates a new dictionary.</p> @@ -220,7 +220,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Return the number of elements in an ordered dictionary. </fsummary> <desc> @@ -229,7 +229,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="store" arity="3"/> + <name name="store" arity="3" since=""/> <fsummary>Store a value in a dictionary.</fsummary> <desc> <p>Stores a <c><anno>Key</anno></c>-<c><anno>Value</anno></c> pair in a @@ -240,7 +240,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert a dictionary to a list of pairs.</fsummary> <desc> <p>Converts a dictionary to a list representation.</p> @@ -248,7 +248,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="update" arity="3"/> + <name name="update" arity="3" since=""/> <fsummary>Update a value in a dictionary.</fsummary> <desc> <p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> @@ -258,7 +258,7 @@ merge(Fun, D1, D2) -> </func> <func> - <name name="update" arity="4"/> + <name name="update" arity="4" since=""/> <fsummary>Update a value in a dictionary.</fsummary> <desc> <p>Updates a value in a dictionary by calling <c><anno>Fun</anno></c> @@ -273,7 +273,7 @@ append(Key, Val, D) -> </func> <func> - <name name="update_counter" arity="3"/> + <name name="update_counter" arity="3" since=""/> <fsummary>Increment a value in a dictionary.</fsummary> <desc> <p>Adds <c><anno>Increment</anno></c> to the value associated with diff --git a/lib/stdlib/doc/src/ordsets.xml b/lib/stdlib/doc/src/ordsets.xml index 11f98c8fb7..fbe334c009 100644 --- a/lib/stdlib/doc/src/ordsets.xml +++ b/lib/stdlib/doc/src/ordsets.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>ordsets.xml</file> </header> - <module>ordsets</module> + <module since="">ordsets</module> <modulesummary>Functions for manipulating sets as ordered lists. </modulesummary> <description> @@ -60,7 +60,7 @@ <funcs> <func> - <name name="add_element" arity="2"/> + <name name="add_element" arity="2" since=""/> <fsummary>Add an element to an <c>Ordset</c>.</fsummary> <desc> <p>Returns a new ordered set formed from <c><anno>Ordset1</anno></c> @@ -69,7 +69,7 @@ </func> <func> - <name name="del_element" arity="2"/> + <name name="del_element" arity="2" since=""/> <fsummary>Remove an element from an <c>Ordset</c>.</fsummary> <desc> <p>Returns <c><anno>Ordset1</anno></c>, but with @@ -78,7 +78,7 @@ </func> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Filter set elements.</fsummary> <desc> <p>Filters elements in <c><anno>Ordset1</anno></c> with boolean function @@ -87,7 +87,7 @@ </func> <func> - <name name="fold" arity="3"/> + <name name="fold" arity="3" since=""/> <fsummary>Fold over set elements.</fsummary> <desc> <p>Folds <c><anno>Function</anno></c> over every element in @@ -97,7 +97,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Convert a list into an <c>Ordset</c>.</fsummary> <desc> <p>Returns an ordered set of the elements in <c><anno>List</anno></c>. @@ -106,7 +106,7 @@ </func> <func> - <name name="intersection" arity="1"/> + <name name="intersection" arity="1" since=""/> <fsummary>Return the intersection of a list of <c>Ordsets</c></fsummary> <desc> <p>Returns the intersection of the non-empty list of sets.</p> @@ -114,7 +114,7 @@ </func> <func> - <name name="intersection" arity="2"/> + <name name="intersection" arity="2" since=""/> <fsummary>Return the intersection of two <c>Ordsets</c>.</fsummary> <desc> <p>Returns the intersection of <c><anno>Ordset1</anno></c> and @@ -123,7 +123,7 @@ </func> <func> - <name name="is_disjoint" arity="2"/> + <name name="is_disjoint" arity="2" since=""/> <fsummary>Check whether two <c>Ordsets</c> are disjoint.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Ordset1</anno></c> and @@ -133,7 +133,7 @@ </func> <func> - <name name="is_element" arity="2"/> + <name name="is_element" arity="2" since=""/> <fsummary>Test for membership of an <c>Ordset</c>.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of @@ -142,7 +142,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since="OTP 21.0"/> <fsummary>Test for empty set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an empty set, @@ -151,7 +151,7 @@ </func> <func> - <name name="is_set" arity="1"/> + <name name="is_set" arity="1" since=""/> <fsummary>Test for an <c>Ordset</c>.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Ordset</anno></c> is an ordered set @@ -160,7 +160,7 @@ </func> <func> - <name name="is_subset" arity="2"/> + <name name="is_subset" arity="2" since=""/> <fsummary>Test for subset.</fsummary> <desc> <p>Returns <c>true</c> when every element of <c><anno>Ordset1</anno></c> @@ -170,7 +170,7 @@ </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Return an empty set.</fsummary> <desc> <p>Returns a new empty ordered set.</p> @@ -178,7 +178,7 @@ </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Return the number of elements in a set.</fsummary> <desc> <p>Returns the number of elements in <c><anno>Ordset</anno></c>.</p> @@ -186,7 +186,7 @@ </func> <func> - <name name="subtract" arity="2"/> + <name name="subtract" arity="2" since=""/> <fsummary>Return the difference of two <c>Ordsets</c>.</fsummary> <desc> <p>Returns only the elements of <c><anno>Ordset1</anno></c> that are not @@ -195,7 +195,7 @@ </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert an <c>Ordset</c> into a list.</fsummary> <desc> <p>Returns the elements of <c><anno>Ordset</anno></c> as a list.</p> @@ -203,7 +203,7 @@ </func> <func> - <name name="union" arity="1"/> + <name name="union" arity="1" since=""/> <fsummary>Return the union of a list of <c>Ordsets</c>.</fsummary> <desc> <p>Returns the merged (union) set of the list of sets.</p> @@ -211,7 +211,7 @@ </func> <func> - <name name="union" arity="2"/> + <name name="union" arity="2" since=""/> <fsummary>Return the union of two <c>Ordsets</c>.</fsummary> <desc> <p>Returns the merged (union) set of <c><anno>Ordset1</anno></c> and diff --git a/lib/stdlib/doc/src/pool.xml b/lib/stdlib/doc/src/pool.xml index 05d12ade28..675ee08bfb 100644 --- a/lib/stdlib/doc/src/pool.xml +++ b/lib/stdlib/doc/src/pool.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>pool</module> + <module since="">pool</module> <modulesummary>Load distribution facility.</modulesummary> <description> <p>This module can be used to run a set of Erlang nodes as a pool @@ -54,7 +54,7 @@ <funcs> <func> - <name name="attach" arity="1"/> + <name name="attach" arity="1" since=""/> <fsummary>Ensure that a pool master is running.</fsummary> <desc> <p>Ensures that a pool master is running and includes @@ -63,7 +63,7 @@ </func> <func> - <name name="get_node" arity="0"/> + <name name="get_node" arity="0" since=""/> <fsummary>Return the node with the expected lowest future load.</fsummary> <desc> <p>Returns the node with the expected lowest future load.</p> @@ -71,7 +71,7 @@ </func> <func> - <name name="get_nodes" arity="0"/> + <name name="get_nodes" arity="0" since=""/> <fsummary>Return a list of the current member nodes of the pool. </fsummary> <desc> @@ -80,7 +80,7 @@ </func> <func> - <name name="pspawn" arity="3"/> + <name name="pspawn" arity="3" since=""/> <fsummary>Spawn a process on the pool node with expected lowest future load.</fsummary> <desc> @@ -90,7 +90,7 @@ </func> <func> - <name name="pspawn_link" arity="3"/> + <name name="pspawn_link" arity="3" since=""/> <fsummary>Spawn and link to a process on the pool node with expected lowest future load.</fsummary> <desc> @@ -100,8 +100,8 @@ </func> <func> - <name name="start" arity="1"/> - <name name="start" arity="2"/> + <name name="start" arity="1" since=""/> + <name name="start" arity="2" since=""/> <fsummary>>Start a new pool.</fsummary> <desc> <p>Starts a new pool. The file <c>.hosts.erlang</c> is read to @@ -122,7 +122,7 @@ </func> <func> - <name name="stop" arity="0"/> + <name name="stop" arity="0" since=""/> <fsummary>Stop the pool and kill all the slave nodes.</fsummary> <desc> <p>Stops the pool and kills all the slave nodes.</p> diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml index b85fab67d5..aeb9f48735 100644 --- a/lib/stdlib/doc/src/proc_lib.xml +++ b/lib/stdlib/doc/src/proc_lib.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>proc_lib</module> + <module since="">proc_lib</module> <modulesummary>Functions for asynchronous and synchronous start of processes adhering to the OTP design principles.</modulesummary> <description> @@ -102,7 +102,7 @@ <funcs> <func> - <name name="format" arity="1"/> + <name name="format" arity="1" since=""/> <fsummary>Format a crash report.</fsummary> <desc> <p>Equivalent to <seealso marker="#format/2"> @@ -111,7 +111,7 @@ </func> <func> - <name name="format" arity="2"/> + <name name="format" arity="2" since="OTP R16B"/> <fsummary>Format a crash report.</fsummary> <desc> <note> @@ -138,7 +138,7 @@ </func> <func> - <name name="format" arity="3"/> + <name name="format" arity="3" since="OTP 18.1"/> <fsummary>Format a crash report.</fsummary> <desc> <note> @@ -162,7 +162,7 @@ </func> <func> - <name name="hibernate" arity="3"/> + <name name="hibernate" arity="3" since=""/> <fsummary>Hibernate a process until a message is sent to it.</fsummary> <desc> <p>This function does the same as (and does call) the @@ -176,8 +176,8 @@ </func> <func> - <name name="init_ack" arity="1"/> - <name name="init_ack" arity="2"/> + <name name="init_ack" arity="1" since=""/> + <name name="init_ack" arity="2" since=""/> <fsummary>Used by a process when it has started.</fsummary> <desc> <p>This function must be used by a process that has been started by @@ -214,7 +214,7 @@ init(Parent) -> </func> <func> - <name name="initial_call" arity="1"/> + <name name="initial_call" arity="1" since=""/> <fsummary>Extract the initial call of a <c>proc_lib</c>spawned process. </fsummary> <desc> @@ -244,10 +244,10 @@ init(Parent) -> </func> <func> - <name name="spawn" arity="1"/> - <name name="spawn" arity="2"/> - <name name="spawn" arity="3"/> - <name name="spawn" arity="4"/> + <name name="spawn" arity="1" since=""/> + <name name="spawn" arity="2" since=""/> + <name name="spawn" arity="3" since=""/> + <name name="spawn" arity="4" since=""/> <fsummary>Spawn a new process.</fsummary> <type variable="Node"/> <type variable="Fun" name_i="1"/> @@ -262,10 +262,10 @@ init(Parent) -> </func> <func> - <name name="spawn_link" arity="1"/> - <name name="spawn_link" arity="2"/> - <name name="spawn_link" arity="3"/> - <name name="spawn_link" arity="4"/> + <name name="spawn_link" arity="1" since=""/> + <name name="spawn_link" arity="2" since=""/> + <name name="spawn_link" arity="3" since=""/> + <name name="spawn_link" arity="4" since=""/> <fsummary>Spawn and link to a new process.</fsummary> <type variable="Node"/> <type variable="Fun" name_i="1"/> @@ -281,10 +281,10 @@ init(Parent) -> </func> <func> - <name name="spawn_opt" arity="2"/> - <name name="spawn_opt" arity="3"/> - <name name="spawn_opt" arity="4"/> - <name name="spawn_opt" arity="5"/> + <name name="spawn_opt" arity="2" since=""/> + <name name="spawn_opt" arity="3" since=""/> + <name name="spawn_opt" arity="4" since=""/> + <name name="spawn_opt" arity="5" since=""/> <fsummary>Spawn a new process with specified options.</fsummary> <type variable="Node"/> <type variable="Fun" name_i="1"/> @@ -306,12 +306,12 @@ init(Parent) -> </func> <func> - <name name="start" arity="3"/> - <name name="start" arity="4"/> - <name name="start" arity="5"/> - <name name="start_link" arity="3"/> - <name name="start_link" arity="4"/> - <name name="start_link" arity="5"/> + <name name="start" arity="3" since=""/> + <name name="start" arity="4" since=""/> + <name name="start" arity="5" since=""/> + <name name="start_link" arity="3" since=""/> + <name name="start_link" arity="4" since=""/> + <name name="start_link" arity="5" since=""/> <fsummary>Start a new process synchronously.</fsummary> <desc> <p>Starts a new process synchronously. Spawns the process and @@ -341,7 +341,7 @@ init(Parent) -> </func> <func> - <name name="stop" arity="1"/> + <name name="stop" arity="1" since="OTP 18.0"/> <fsummary>Terminate a process synchronously.</fsummary> <type variable="Process"/> <desc> @@ -351,7 +351,7 @@ init(Parent) -> </func> <func> - <name name="stop" arity="3"/> + <name name="stop" arity="3" since="OTP 18.0"/> <fsummary>Terminate a process synchronously.</fsummary> <type variable="Process"/> <type variable="Reason"/> @@ -375,7 +375,7 @@ init(Parent) -> </func> <func> - <name name="translate_initial_call" arity="1"/> + <name name="translate_initial_call" arity="1" since=""/> <fsummary>Extract and translate the initial call of a <c>proc_lib</c>spawned process.</fsummary> <desc> diff --git a/lib/stdlib/doc/src/proplists.xml b/lib/stdlib/doc/src/proplists.xml index f9a54bf804..9d7eb55a7e 100644 --- a/lib/stdlib/doc/src/proplists.xml +++ b/lib/stdlib/doc/src/proplists.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>proplists.xml</file> </header> - <module>proplists</module> + <module since="">proplists</module> <modulesummary>Support functions for property lists.</modulesummary> <description> <p>Property lists are ordinary lists containing entries in the form @@ -61,7 +61,7 @@ <funcs> <func> - <name name="append_values" arity="2"/> + <name name="append_values" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Similar to @@ -79,7 +79,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code> </func> <func> - <name name="compact" arity="1"/> + <name name="compact" arity="1" since=""/> <fsummary></fsummary> <desc> <p>Minimizes the representation of all entries in the list. This is @@ -91,7 +91,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code> </func> <func> - <name name="delete" arity="2"/> + <name name="delete" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Deletes all entries associated with <c><anno>Key</anno></c> from @@ -100,7 +100,7 @@ append_values(a, [{a, [1,2]}, {b, 0}, {a, 3}, {c, -1}, {a, [4]}])</code> </func> <func> - <name name="expand" arity="2"/> + <name name="expand" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Expands particular properties to corresponding sets of @@ -133,7 +133,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="get_all_values" arity="2"/> + <name name="get_all_values" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Similar to @@ -145,7 +145,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="get_bool" arity="2"/> + <name name="get_bool" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Returns the value of a boolean key/value option. If @@ -159,7 +159,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="get_keys" arity="1"/> + <name name="get_keys" arity="1" since=""/> <fsummary></fsummary> <desc> <p>Returns an unordered list of the keys used in @@ -168,7 +168,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="get_value" arity="2"/> + <name name="get_value" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Equivalent to @@ -177,7 +177,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="get_value" arity="3"/> + <name name="get_value" arity="3" since=""/> <fsummary></fsummary> <desc> <p>Returns the value of a simple key/value property in @@ -194,7 +194,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="is_defined" arity="2"/> + <name name="is_defined" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Returns <c>true</c> if <c><anno>List</anno></c> contains at least @@ -204,7 +204,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="lookup" arity="2"/> + <name name="lookup" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Returns the first entry associated with <c><anno>Key</anno></c> in @@ -219,7 +219,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="lookup_all" arity="2"/> + <name name="lookup_all" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Returns the list of all entries associated with @@ -231,7 +231,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="normalize" arity="2"/> + <name name="normalize" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Passes <c><anno>ListIn</anno></c> through a sequence of @@ -263,7 +263,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="property" arity="1"/> + <name name="property" arity="1" since=""/> <fsummary></fsummary> <desc> <p>Creates a normal form (minimal) representation of a property. If @@ -276,7 +276,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="property" arity="2"/> + <name name="property" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Creates a normal form (minimal) representation of a simple key/value @@ -289,7 +289,7 @@ expand([{{foo, true}, [bar, baz]}], [{foo, false}, fie, foo, fum])</code> </func> <func> - <name name="split" arity="2"/> + <name name="split" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Partitions <c><anno>List</anno></c> into a list of sublists and a @@ -310,7 +310,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code> </func> <func> - <name name="substitute_aliases" arity="2"/> + <name name="substitute_aliases" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Substitutes keys of properties. For each entry in @@ -332,7 +332,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code> </func> <func> - <name name="substitute_negations" arity="2"/> + <name name="substitute_negations" arity="2" since=""/> <fsummary></fsummary> <desc> <p>Substitutes keys of boolean-valued properties and @@ -360,7 +360,7 @@ split([{c, 2}, {e, 1}, a, {c, 3, 4}, d, {b, 5}, b], [a, b, c])</code> </func> <func> - <name name="unfold" arity="1"/> + <name name="unfold" arity="1" since=""/> <fsummary></fsummary> <desc> <p>Unfolds all occurrences of atoms in <c><anno>ListIn</anno></c> to diff --git a/lib/stdlib/doc/src/qlc.xml b/lib/stdlib/doc/src/qlc.xml index fe14a6334c..fe60c2e9bb 100644 --- a/lib/stdlib/doc/src/qlc.xml +++ b/lib/stdlib/doc/src/qlc.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>qlc.xml</file> </header> - <module>qlc</module> + <module since="">qlc</module> <modulesummary>Query interface to Mnesia, ETS, Dets, and so on. </modulesummary> <description> @@ -720,7 +720,7 @@ ets:match_spec_run(ets:lookup(86033, {2,2}), <funcs> <func> - <name name="append" arity="1"/> + <name name="append" arity="1" since=""/> <fsummary>Return a query handle.</fsummary> <desc> <p>Returns a query handle. When evaluating query handle @@ -731,7 +731,7 @@ ets:match_spec_run(ets:lookup(86033, {2,2}), </func> <func> - <name name="append" arity="2"/> + <name name="append" arity="2" since=""/> <fsummary>Return a query handle.</fsummary> <desc> <p>Returns a query handle. When evaluating query handle @@ -744,8 +744,8 @@ ets:match_spec_run(ets:lookup(86033, {2,2}), </func> <func> - <name name="cursor" arity="1"/> - <name name="cursor" arity="2"/> + <name name="cursor" arity="1" since=""/> + <name name="cursor" arity="2" since=""/> <fsummary>Create a query cursor.</fsummary> <desc> <p>Creates a query cursor and @@ -777,7 +777,7 @@ ok</pre> </func> <func> - <name name="delete_cursor" arity="1"/> + <name name="delete_cursor" arity="1" since=""/> <fsummary>Delete a query cursor.</fsummary> <desc> <p>Deletes a query cursor. Only the owner of the cursor can @@ -786,10 +786,10 @@ ok</pre> </func> <func> - <name name="e" arity="1"/> - <name name="e" arity="2"/> - <name name="eval" arity="1"/> - <name name="eval" arity="2"/> + <name name="e" arity="1" since=""/> + <name name="e" arity="2" since=""/> + <name name="eval" arity="1" since=""/> + <name name="eval" arity="2" since=""/> <fsummary>Return all answers to a query.</fsummary> <desc> <p>Evaluates a query handle in the @@ -805,8 +805,8 @@ ok</pre> </func> <func> - <name name="fold" arity="3"/> - <name name="fold" arity="4"/> + <name name="fold" arity="3" since=""/> + <name name="fold" arity="4" since=""/> <fsummary>Fold a function over the answers to a query.</fsummary> <desc> <p>Calls <c><anno>Function</anno></c> on successive answers to @@ -830,7 +830,7 @@ ok</pre> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Return an English description of a an error tuple.</fsummary> <desc> <p>Returns a descriptive string in English of an error tuple @@ -841,8 +841,8 @@ ok</pre> </func> <func> - <name name="info" arity="1"/> - <name name="info" arity="2"/> + <name name="info" arity="1" since=""/> + <name name="info" arity="2" since=""/> <fsummary>Return code describing a query handle.</fsummary> <desc> <p>Returns information about a @@ -946,8 +946,8 @@ end</pre> </func> <func> - <name name="keysort" arity="2"/> - <name name="keysort" arity="3"/> + <name name="keysort" arity="2" since=""/> + <name name="keysort" arity="3" since=""/> <fsummary>Return a query handle.</fsummary> <desc> <p>Returns a query handle. When evaluating query handle @@ -967,8 +967,8 @@ end</pre> </func> <func> - <name name="next_answers" arity="1"/> - <name name="next_answers" arity="2"/> + <name name="next_answers" arity="1" since=""/> + <name name="next_answers" arity="2" since=""/> <fsummary>Return some or all answers to a query.</fsummary> <desc> <p>Returns some or all of the remaining answers to a query @@ -983,8 +983,8 @@ end</pre> </func> <func> - <name name="q" arity="1"/> - <name name="q" arity="2"/> + <name name="q" arity="1" since=""/> + <name name="q" arity="2" since=""/> <fsummary>Return a handle for a query list comprehension.</fsummary> <desc> <p>Returns a query handle for a QLC. @@ -1188,8 +1188,8 @@ ets:match_spec_run( </func> <func> - <name name="sort" arity="1"/> - <name name="sort" arity="2"/> + <name name="sort" arity="1" since=""/> + <name name="sort" arity="2" since=""/> <fsummary>Return a query handle.</fsummary> <desc> <p>Returns a query handle. When evaluating query handle @@ -1208,9 +1208,9 @@ ets:match_spec_run( </func> <func> - <name name="string_to_handle" arity="1"/> - <name name="string_to_handle" arity="2"/> - <name name="string_to_handle" arity="3"/> + <name name="string_to_handle" arity="1" since=""/> + <name name="string_to_handle" arity="2" since=""/> + <name name="string_to_handle" arity="3" since=""/> <fsummary>Return a handle for a query list comprehension.</fsummary> <desc> <p>A string version of <seealso marker="#q/1"><c>q/1,2</c></seealso>. @@ -1238,7 +1238,7 @@ ets:match_spec_run( </func> <func> - <name name="table" arity="2"/> + <name name="table" arity="2" since=""/> <fsummary>Return a query handle for a table.</fsummary> <desc> <p>Returns a query handle for a QLC table. diff --git a/lib/stdlib/doc/src/queue.xml b/lib/stdlib/doc/src/queue.xml index 9f3aff03a3..83a8afea81 100644 --- a/lib/stdlib/doc/src/queue.xml +++ b/lib/stdlib/doc/src/queue.xml @@ -32,7 +32,7 @@ <rev>B</rev> <file>queue.xml</file> </header> - <module>queue</module> + <module since="">queue</module> <modulesummary>Abstract data type for FIFO queues.</modulesummary> <description> <p>This module provides (double-ended) FIFO queues @@ -113,7 +113,7 @@ <funcs> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Filter a queue.</fsummary> <desc> <p>Returns a queue <c><anno>Q2</anno></c> that is the result of calling @@ -134,7 +134,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Convert a list to a queue.</fsummary> <desc> <p>Returns a queue containing the items in <c><anno>L</anno></c> in the @@ -144,7 +144,7 @@ </func> <func> - <name name="in" arity="2"/> + <name name="in" arity="2" since=""/> <fsummary>Insert an item at the rear of a queue.</fsummary> <desc> <p>Inserts <c><anno>Item</anno></c> at the rear of queue @@ -154,7 +154,7 @@ </func> <func> - <name name="in_r" arity="2"/> + <name name="in_r" arity="2" since=""/> <fsummary>Insert an item at the front of a queue.</fsummary> <desc> <p>Inserts <c><anno>Item</anno></c> at the front of queue @@ -164,7 +164,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since=""/> <fsummary>Test if a queue is empty.</fsummary> <desc> <p>Tests if <c><anno>Q</anno></c> is empty and returns <c>true</c> if @@ -173,7 +173,7 @@ </func> <func> - <name name="is_queue" arity="1"/> + <name name="is_queue" arity="1" since=""/> <fsummary>Test if a term is a queue.</fsummary> <desc> <p>Tests if <c><anno>Term</anno></c> is a queue and returns <c>true</c> @@ -182,7 +182,7 @@ </func> <func> - <name name="join" arity="2"/> + <name name="join" arity="2" since=""/> <fsummary>Join two queues.</fsummary> <desc> <p>Returns a queue <c><anno>Q3</anno></c> that is the result of joining @@ -192,7 +192,7 @@ </func> <func> - <name name="len" arity="1"/> + <name name="len" arity="1" since=""/> <fsummary>Get the length of a queue.</fsummary> <desc> <p>Calculates and returns the length of queue <c><anno>Q</anno></c>.</p> @@ -200,7 +200,7 @@ </func> <func> - <name name="member" arity="2"/> + <name name="member" arity="2" since=""/> <fsummary>Test if an item is in a queue.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Item</anno></c> matches some element @@ -209,7 +209,7 @@ </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Create an empty queue.</fsummary> <desc> <p>Returns an empty queue.</p> @@ -217,7 +217,7 @@ </func> <func> - <name name="out" arity="1"/> + <name name="out" arity="1" since=""/> <fsummary>Remove the front item from a queue.</fsummary> <desc> <p>Removes the item at the front of queue <c><anno>Q1</anno></c>. @@ -230,7 +230,7 @@ </func> <func> - <name name="out_r" arity="1"/> + <name name="out_r" arity="1" since=""/> <fsummary>Remove the rear item from a queue.</fsummary> <desc> <p>Removes the item at the rear of queue <c><anno>Q1</anno></c>. @@ -242,7 +242,7 @@ </func> <func> - <name name="reverse" arity="1"/> + <name name="reverse" arity="1" since=""/> <fsummary>Reverse a queue.</fsummary> <desc> <p>Returns a queue <c><anno>Q2</anno></c> containing the items of @@ -251,7 +251,7 @@ </func> <func> - <name name="split" arity="2"/> + <name name="split" arity="2" since=""/> <fsummary>Split a queue in two.</fsummary> <desc> <p>Splits <c><anno>Q1</anno></c> in two. The <c><anno>N</anno></c> @@ -261,7 +261,7 @@ </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert a queue to a list.</fsummary> <desc> <p>Returns a list of the items in the queue in the same order; @@ -276,7 +276,7 @@ <funcs> <func> - <name name="drop" arity="1"/> + <name name="drop" arity="1" since=""/> <fsummary>Remove the front item from a queue.</fsummary> <desc> <p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing @@ -286,7 +286,7 @@ </func> <func> - <name name="drop_r" arity="1"/> + <name name="drop_r" arity="1" since=""/> <fsummary>Remove the rear item from a queue.</fsummary> <desc> <p>Returns a queue <c><anno>Q2</anno></c> that is the result of removing @@ -296,7 +296,7 @@ </func> <func> - <name name="get" arity="1"/> + <name name="get" arity="1" since=""/> <fsummary>Return the front item of a queue.</fsummary> <desc> <p>Returns <c><anno>Item</anno></c> at the front of queue @@ -306,7 +306,7 @@ </func> <func> - <name name="get_r" arity="1"/> + <name name="get_r" arity="1" since=""/> <fsummary>Return the rear item of a queue.</fsummary> <desc> <p>Returns <c><anno>Item</anno></c> at the rear of queue @@ -316,7 +316,7 @@ </func> <func> - <name name="peek" arity="1"/> + <name name="peek" arity="1" since=""/> <fsummary>Return the front item of a queue.</fsummary> <desc> <p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where @@ -326,7 +326,7 @@ </func> <func> - <name name="peek_r" arity="1"/> + <name name="peek_r" arity="1" since=""/> <fsummary>Return the rear item of a queue.</fsummary> <desc> <p>Returns tuple <c>{value, <anno>Item</anno>}</c>, where @@ -342,7 +342,7 @@ <funcs> <func> - <name name="cons" arity="2"/> + <name name="cons" arity="2" since=""/> <fsummary>Insert an item at the head of a queue.</fsummary> <desc> <p>Inserts <c><anno>Item</anno></c> at the head of queue @@ -352,7 +352,7 @@ </func> <func> - <name name="daeh" arity="1"/> + <name name="daeh" arity="1" since=""/> <fsummary>Return the tail item of a queue.</fsummary> <desc> <p>Returns the tail item of queue <c><anno>Q</anno></c>.</p> @@ -361,7 +361,7 @@ </func> <func> - <name name="head" arity="1"/> + <name name="head" arity="1" since=""/> <fsummary>Return the item at the head of a queue.</fsummary> <desc> <p>Returns <c><anno>Item</anno></c> from the head of queue @@ -371,7 +371,7 @@ </func> <func> - <name name="init" arity="1"/> + <name name="init" arity="1" since=""/> <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 @@ -381,7 +381,7 @@ </func> <func> - <name name="lait" arity="1"/> + <name name="lait" arity="1" since=""/> <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 @@ -392,7 +392,7 @@ </func> <func> - <name name="last" arity="1"/> + <name name="last" arity="1" since=""/> <fsummary>Return the tail item of a queue.</fsummary> <desc> <p>Returns the tail item of queue <c><anno>Q</anno></c>.</p> @@ -401,7 +401,7 @@ </func> <func> - <name name="liat" arity="1"/> + <name name="liat" arity="1" since=""/> <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 @@ -411,7 +411,7 @@ </func> <func> - <name name="snoc" arity="2"/> + <name name="snoc" arity="2" since=""/> <fsummary>Insert an item at the tail of a queue.</fsummary> <desc> <p>Inserts <c><anno>Item</anno></c> as the tail item of queue @@ -421,7 +421,7 @@ </func> <func> - <name name="tail" arity="1"/> + <name name="tail" arity="1" since=""/> <fsummary>Remove the head 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/rand.xml b/lib/stdlib/doc/src/rand.xml index 8e657698c6..b4737b48f8 100644 --- a/lib/stdlib/doc/src/rand.xml +++ b/lib/stdlib/doc/src/rand.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>rand.xml</file> </header> - <module>rand</module> + <module since="OTP 18.0">rand</module> <modulesummary>Pseudo random number generation.</modulesummary> <description> <p> @@ -349,7 +349,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> <funcs> <func> - <name name="export_seed" arity="0"/> + <name name="export_seed" arity="0" since="OTP 18.0"/> <fsummary>Export the random number generation state.</fsummary> <desc><marker id="export_seed-0"/> <p>Returns the random number state in an external format. @@ -358,7 +358,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="export_seed_s" arity="1"/> + <name name="export_seed_s" arity="1" since="OTP 18.0"/> <fsummary>Export the random number generation state.</fsummary> <desc><marker id="export_seed_s-1"/> <p>Returns the random number generator state in an external format. @@ -367,7 +367,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="jump" arity="0"/> + <name name="jump" arity="0" since="OTP 20.0"/> <fsummary>Return the seed after performing jump calculation to the state in the process dictionary.</fsummary> <desc><marker id="jump-0" /> @@ -382,7 +382,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="jump" arity="1"/> + <name name="jump" arity="1" since="OTP 20.0"/> <fsummary>Return the seed after performing jump calculation.</fsummary> <desc><marker id="jump-1" /> <p>Returns the state after performing jump calculation @@ -394,7 +394,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="normal" arity="0"/> + <name name="normal" arity="0" since="OTP 18.0"/> <fsummary>Return a standard normal distributed random float.</fsummary> <desc> <p>Returns a standard normal deviate float (that is, the mean @@ -404,7 +404,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="normal" arity="2"/> + <name name="normal" arity="2" since="OTP 20.0"/> <fsummary>Return a normal distributed random float.</fsummary> <desc> <p>Returns a normal N(Mean, Variance) deviate float @@ -413,7 +413,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="normal_s" arity="1"/> + <name name="normal_s" arity="1" since="OTP 18.0"/> <fsummary>Return a standard normal distributed random float.</fsummary> <desc> <p>Returns, for a specified state, a standard normal @@ -423,7 +423,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="normal_s" arity="3"/> + <name name="normal_s" arity="3" since="OTP 20.0"/> <fsummary>Return a normal distributed random float.</fsummary> <desc> <p>Returns, for a specified state, a normal N(Mean, Variance) @@ -432,7 +432,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="seed" arity="1"/> + <name name="seed" arity="1" since="OTP 18.0"/> <fsummary>Seed random number generator.</fsummary> <desc> <marker id="seed-1"/> @@ -448,7 +448,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="seed" arity="2"/> + <name name="seed" arity="2" since="OTP 18.0"/> <fsummary>Seed the random number generation.</fsummary> <desc> <p>Seeds random number generation with the specified algorithm and @@ -457,7 +457,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="seed_s" arity="1"/> + <name name="seed_s" arity="1" since="OTP 18.0"/> <fsummary>Seed random number generator.</fsummary> <desc> <p> @@ -472,7 +472,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="seed_s" arity="2"/> + <name name="seed_s" arity="2" since="OTP 18.0"/> <fsummary>Seed the random number generation.</fsummary> <desc> <p>Seeds random number generation with the specified algorithm and @@ -481,7 +481,7 @@ tests. We suggest to use a sign test to extract a random Boolean value.</pre> </func> <func> - <name name="uniform" arity="0"/> + <name name="uniform" arity="0" since="OTP 18.0"/> <fsummary>Return a random float.</fsummary> <desc><marker id="uniform-0"/> <p> @@ -517,7 +517,7 @@ end.</pre> </func> <func> - <name name="uniform_real" arity="0"/> + <name name="uniform_real" arity="0" since="OTP 21.0"/> <fsummary>Return a random float.</fsummary> <desc><marker id="uniform_real-0"/> <p> @@ -553,7 +553,7 @@ end.</pre> </func> <func> - <name name="uniform" arity="1"/> + <name name="uniform" arity="1" since="OTP 18.0"/> <fsummary>Return a random integer.</fsummary> <desc><marker id="uniform-1"/> <p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>, @@ -564,7 +564,7 @@ end.</pre> </func> <func> - <name name="uniform_s" arity="1"/> + <name name="uniform_s" arity="1" since="OTP 18.0"/> <fsummary>Return a random float.</fsummary> <desc> <p> @@ -600,7 +600,7 @@ end.</pre> </func> <func> - <name name="uniform_real_s" arity="1"/> + <name name="uniform_real_s" arity="1" since="OTP 21.0"/> <fsummary>Return a random float.</fsummary> <desc> <p> @@ -662,7 +662,7 @@ end.</pre> </func> <func> - <name name="uniform_s" arity="2"/> + <name name="uniform_s" arity="2" since="OTP 18.0"/> <fsummary>Return a random integer.</fsummary> <desc> <p>Returns, for a specified integer <c><anno>N</anno> >= 1</c> diff --git a/lib/stdlib/doc/src/random.xml b/lib/stdlib/doc/src/random.xml index 8d090d20b3..f0261ed009 100644 --- a/lib/stdlib/doc/src/random.xml +++ b/lib/stdlib/doc/src/random.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>random.xml</file> </header> - <module>random</module> + <module since="">random</module> <modulesummary>Pseudo-random number generation.</modulesummary> <description> <p>This module provides a random number generator. The method is attributed @@ -73,7 +73,7 @@ <funcs> <func> - <name name="seed" arity="0"/> + <name name="seed" arity="0" since=""/> <fsummary>Seed random number generation with default values.</fsummary> <desc> <p>Seeds random number generation with default (fixed) values @@ -82,7 +82,7 @@ </func> <func> - <name name="seed" arity="1"/> + <name name="seed" arity="1" since=""/> <fsummary>Seed random number generator.</fsummary> <desc> <p><c>seed({<anno>A1</anno>, <anno>A2</anno>, <anno>A3</anno>})</c> @@ -92,7 +92,7 @@ </func> <func> - <name name="seed" arity="3"/> + <name name="seed" arity="3" since=""/> <fsummary>Seed random number generator.</fsummary> <desc> <p>Seeds random number generation with integer values in the process @@ -116,7 +116,7 @@ random:seed(erlang:phash2([node()]), </func> <func> - <name name="seed0" arity="0"/> + <name name="seed0" arity="0" since=""/> <fsummary>Return default state for random number generation.</fsummary> <desc> <p>Returns the default state.</p> @@ -124,7 +124,7 @@ random:seed(erlang:phash2([node()]), </func> <func> - <name name="uniform" arity="0"/> + <name name="uniform" arity="0" since=""/> <fsummary>Return a random float.</fsummary> <desc> <p>Returns a random float uniformly distributed between <c>0.0</c> @@ -133,7 +133,7 @@ random:seed(erlang:phash2([node()]), </func> <func> - <name name="uniform" arity="1"/> + <name name="uniform" arity="1" since=""/> <fsummary>Return a random integer.</fsummary> <desc> <p>Returns, for a specified integer <c><anno>N</anno> >= 1</c>, @@ -144,7 +144,7 @@ random:seed(erlang:phash2([node()]), </func> <func> - <name name="uniform_s" arity="1"/> + <name name="uniform_s" arity="1" since=""/> <fsummary>Return a random float.</fsummary> <desc> <p>Returns, for a specified state, a random float uniformly @@ -153,7 +153,7 @@ random:seed(erlang:phash2([node()]), </func> <func> - <name name="uniform_s" arity="2"/> + <name name="uniform_s" arity="2" since=""/> <fsummary>Return a random integer.</fsummary> <desc> <p>Returns, for a specified integer <c><anno>N</anno> >= 1</c> and a diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml index 078ca0e38c..b04434492d 100644 --- a/lib/stdlib/doc/src/re.xml +++ b/lib/stdlib/doc/src/re.xml @@ -34,7 +34,7 @@ <rev>A</rev> <file>re.xml</file> </header> - <module>re</module> + <module since="">re</module> <modulesummary>Perl-like regular expressions for Erlang.</modulesummary> <description> <p>This module contains regular expression matching functions for @@ -79,7 +79,7 @@ <funcs> <func> - <name name="version" arity="0"/> + <name name="version" arity="0" since="OTP 20.0"/> <fsummary>Gives the PCRE version of the system in a string format</fsummary> <desc> <p>The return of this function is a string with the PCRE version of the system that was used in the Erlang/OTP compilation.</p> @@ -87,7 +87,7 @@ </func> <func> - <name name="compile" arity="1"/> + <name name="compile" arity="1" since=""/> <fsummary>Compile a regular expression into a match program</fsummary> <desc> <p>The same as <c>compile(<anno>Regexp</anno>,[])</c></p> @@ -95,7 +95,7 @@ </func> <func> - <name name="compile" arity="2"/> + <name name="compile" arity="2" since=""/> <fsummary>Compile a regular expression into a match program.</fsummary> <desc> <p>Compiles a regular expression, with the syntax @@ -304,7 +304,7 @@ </func> <func> - <name name="inspect" arity="2"/> + <name name="inspect" arity="2" since="OTP 17.0"/> <fsummary>Inspects a compiled regular expression.</fsummary> <desc> <p>Takes a compiled regular expression and an item, and returns the @@ -348,7 +348,7 @@ </func> <func> - <name name="replace" arity="3"/> + <name name="replace" arity="3" since=""/> <fsummary>Match a subject against regular expression and replace matching elements with Replacement.</fsummary> <desc> @@ -358,7 +358,7 @@ </func> <func> - <name name="replace" arity="4"/> + <name name="replace" arity="4" since=""/> <fsummary>Match a subject against regular expression and replace matching elements with Replacement.</fsummary> <desc> @@ -408,7 +408,7 @@ re:replace("abcd","c","[\\&]",[{return,list}]).</code> </func> <func> - <name name="run" arity="2"/> + <name name="run" arity="2" since=""/> <fsummary>Match a subject against regular expression and capture subpatterns.</fsummary> <desc> @@ -417,7 +417,7 @@ re:replace("abcd","c","[\\&]",[{return,list}]).</code> </func> <func> - <name name="run" arity="3"/> + <name name="run" arity="3" since=""/> <fsummary>Match a subject against regular expression and capture subpatterns.</fsummary> <type_desc variable="CompileOpt">See <seealso marker="#compile_options"> @@ -992,7 +992,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code> </func> <func> - <name name="split" arity="2"/> + <name name="split" arity="2" since=""/> <fsummary>Split a string by tokens specified as a regular expression. </fsummary> <desc> @@ -1001,7 +1001,7 @@ re:run("cacb","c(a|b)",[global,{capture,[1],list}]).</code> </func> <func> - <name name="split" arity="3"/> + <name name="split" arity="3" since=""/> <fsummary>Split a string by tokens specified as a regular expression</fsummary> <type_desc variable="CompileOpt">See <seealso marker="#compile_options"> <c>compile/2</c></seealso>.</type_desc> diff --git a/lib/stdlib/doc/src/sets.xml b/lib/stdlib/doc/src/sets.xml index 8db3e1e623..07ce41b7a7 100644 --- a/lib/stdlib/doc/src/sets.xml +++ b/lib/stdlib/doc/src/sets.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>sets.xml</file> </header> - <module>sets</module> + <module since="">sets</module> <modulesummary>Functions for set manipulation.</modulesummary> <description> <p>Sets are collections of elements with no duplicate elements. @@ -59,7 +59,7 @@ <funcs> <func> - <name name="add_element" arity="2"/> + <name name="add_element" arity="2" since=""/> <fsummary>Add an element to a <c>Set</c>.</fsummary> <desc> <p>Returns a new set formed from <c><anno>Set1</anno></c> with @@ -68,7 +68,7 @@ </func> <func> - <name name="del_element" arity="2"/> + <name name="del_element" arity="2" since=""/> <fsummary>Remove an element from a <c>Set</c>.</fsummary> <desc> <p>Returns <c><anno>Set1</anno></c>, but with @@ -77,7 +77,7 @@ </func> <func> - <name name="filter" arity="2"/> + <name name="filter" arity="2" since=""/> <fsummary>Filter set elements.</fsummary> <desc> <p>Filters elements in <c><anno>Set1</anno></c> with boolean function @@ -86,7 +86,7 @@ </func> <func> - <name name="fold" arity="3"/> + <name name="fold" arity="3" since=""/> <fsummary>Fold over set elements.</fsummary> <desc> <p>Folds <c><anno>Function</anno></c> over every element in @@ -96,7 +96,7 @@ </func> <func> - <name name="from_list" arity="1"/> + <name name="from_list" arity="1" since=""/> <fsummary>Convert a list into a <c>Set</c>.</fsummary> <desc> <p>Returns a set of the elements in <c><anno>List</anno></c>.</p> @@ -104,7 +104,7 @@ </func> <func> - <name name="intersection" arity="1"/> + <name name="intersection" arity="1" since=""/> <fsummary>Return the intersection of a list of <c>Sets</c>.</fsummary> <desc> <p>Returns the intersection of the non-empty list of sets.</p> @@ -112,7 +112,7 @@ </func> <func> - <name name="intersection" arity="2"/> + <name name="intersection" arity="2" since=""/> <fsummary>Return the intersection of two <c>Sets</c>.</fsummary> <desc> <p>Returns the intersection of <c><anno>Set1</anno></c> and @@ -121,7 +121,7 @@ </func> <func> - <name name="is_disjoint" arity="2"/> + <name name="is_disjoint" arity="2" since=""/> <fsummary>Check whether two <c>Sets</c> are disjoint.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set1</anno></c> and @@ -131,7 +131,7 @@ </func> <func> - <name name="is_element" arity="2"/> + <name name="is_element" arity="2" since=""/> <fsummary>Test for membership of a <c>Set</c>.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Element</anno></c> is an element of @@ -140,7 +140,7 @@ </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since="OTP 21.0"/> <fsummary>Test for empty set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set</anno></c> is an empty set, @@ -149,7 +149,7 @@ </func> <func> - <name name="is_set" arity="1"/> + <name name="is_set" arity="1" since=""/> <fsummary>Test for a <c>Set</c>.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set</anno></c> is a set of @@ -158,7 +158,7 @@ </func> <func> - <name name="is_subset" arity="2"/> + <name name="is_subset" arity="2" since=""/> <fsummary>Test for subset.</fsummary> <desc> <p>Returns <c>true</c> when every element of <c><anno>Set1</anno></c> is @@ -167,7 +167,7 @@ </func> <func> - <name name="new" arity="0"/> + <name name="new" arity="0" since=""/> <fsummary>Return an empty set.</fsummary> <desc> <p>Returns a new empty set.</p> @@ -175,7 +175,7 @@ </func> <func> - <name name="size" arity="1"/> + <name name="size" arity="1" since=""/> <fsummary>Return the number of elements in a set.</fsummary> <desc> <p>Returns the number of elements in <c><anno>Set</anno></c>.</p> @@ -183,7 +183,7 @@ </func> <func> - <name name="subtract" arity="2"/> + <name name="subtract" arity="2" since=""/> <fsummary>Return the difference of two <c>Sets</c>.</fsummary> <desc> <p>Returns only the elements of <c><anno>Set1</anno></c> that are not @@ -192,7 +192,7 @@ </func> <func> - <name name="to_list" arity="1"/> + <name name="to_list" arity="1" since=""/> <fsummary>Convert a <c>Set</c>into a list.</fsummary> <desc> <p>Returns the elements of <c><anno>Set</anno></c> as a list. @@ -201,7 +201,7 @@ </func> <func> - <name name="union" arity="1"/> + <name name="union" arity="1" since=""/> <fsummary>Return the union of a list of <c>Sets</c>.</fsummary> <desc> <p>Returns the merged (union) set of the list of sets.</p> @@ -209,7 +209,7 @@ </func> <func> - <name name="union" arity="2"/> + <name name="union" arity="2" since=""/> <fsummary>Return the union of two <c>Sets</c>.</fsummary> <desc> <p>Returns the merged (union) set of <c><anno>Set1</anno></c> and diff --git a/lib/stdlib/doc/src/shell.xml b/lib/stdlib/doc/src/shell.xml index 2593d3690b..50a0968531 100644 --- a/lib/stdlib/doc/src/shell.xml +++ b/lib/stdlib/doc/src/shell.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>shell.xml</file> </header> - <module>shell</module> + <module since="">shell</module> <modulesummary>The Erlang shell.</modulesummary> <description> <p>This module provides an Erlang shell.</p> @@ -874,7 +874,7 @@ q - quit erlang <funcs> <func> - <name>catch_exception(Bool) -> boolean()</name> + <name since="">catch_exception(Bool) -> boolean()</name> <fsummary>Set the exception handling of the shell.</fsummary> <type> <v>Bool = boolean()</v> @@ -892,7 +892,7 @@ q - quit erlang </func> <func> - <name name="history" arity="1"/> + <name name="history" arity="1" since=""/> <fsummary>Set the number of previous commands to keep.</fsummary> <desc> <p>Sets the number of previous commands to keep in the @@ -902,7 +902,7 @@ q - quit erlang </func> <func> - <name name="prompt_func" arity="1"/> + <name name="prompt_func" arity="1" since="OTP R13B04"/> <fsummary>Set the shell prompt.</fsummary> <desc> <p>Sets the shell prompt function to <c><anno>PromptFunc</anno></c>. @@ -911,7 +911,7 @@ q - quit erlang </func> <func> - <name name="results" arity="1"/> + <name name="results" arity="1" since=""/> <fsummary>Set the number of previous results to keep.</fsummary> <desc> <p>Sets the number of results from previous commands to keep in @@ -921,7 +921,7 @@ q - quit erlang </func> <func> - <name name="start_restricted" arity="1"/> + <name name="start_restricted" arity="1" since=""/> <fsummary>Exit a normal shell and starts a restricted shell.</fsummary> <desc> <p>Exits a normal shell and starts a restricted shell. @@ -936,7 +936,7 @@ q - quit erlang </func> <func> - <name name="stop_restricted" arity="0"/> + <name name="stop_restricted" arity="0" since=""/> <fsummary>Exit a restricted shell and starts a normal shell.</fsummary> <desc> <p>Exits a restricted shell and starts a normal shell. The function @@ -945,7 +945,7 @@ q - quit erlang </func> <func> - <name name="strings" arity="1"/> + <name name="strings" arity="1" since="OTP R16B"/> <fsummary>Set the shell's string recognition flag.</fsummary> <desc> <p>Sets pretty printing of lists to <c><anno>Strings</anno></c>. diff --git a/lib/stdlib/doc/src/slave.xml b/lib/stdlib/doc/src/slave.xml index e53ec8231b..80fb28b548 100644 --- a/lib/stdlib/doc/src/slave.xml +++ b/lib/stdlib/doc/src/slave.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>slave</module> + <module since="">slave</module> <modulesummary>Functions for starting and controlling slave nodes. </modulesummary> <description> @@ -68,7 +68,7 @@ <funcs> <func> - <name>pseudo([Master | ServerList]) -> ok</name> + <name since="">pseudo([Master | ServerList]) -> ok</name> <fsummary>Start a number of pseudo servers.</fsummary> <type> <v>Master = node()</v> @@ -84,7 +84,7 @@ </func> <func> - <name name="pseudo" arity="2"/> + <name name="pseudo" arity="2" since=""/> <fsummary>Start a number of pseudo servers.</fsummary> <desc> <p>Starts a number of pseudo servers. A pseudo server is a @@ -102,7 +102,7 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code> </func> <func> - <name name="relay" arity="1"/> + <name name="relay" arity="1" since=""/> <fsummary>Run a pseudo server.</fsummary> <desc> <p>Runs a pseudo server. This function never returns any value @@ -113,9 +113,9 @@ rpc:call(N, slave, pseudo, [node(), [pxw_server]]).</code> </func> <func> - <name name="start" arity="1"/> - <name name="start" arity="2"/> - <name name="start" arity="3"/> + <name name="start" arity="1" since=""/> + <name name="start" arity="2" since=""/> + <name name="start" arity="3" since=""/> <fsummary>Start a slave node on a host.</fsummary> <desc> <p>Starts a slave node on host <c><anno>Host</anno></c>. Host names @@ -178,9 +178,9 @@ slave:start(H, Name, Arg).</code> </func> <func> - <name name="start_link" arity="1"/> - <name name="start_link" arity="2"/> - <name name="start_link" arity="3"/> + <name name="start_link" arity="1" since=""/> + <name name="start_link" arity="2" since=""/> + <name name="start_link" arity="3" since=""/> <fsummary>Start and link to a slave node on a host.</fsummary> <desc> <p>Starts a slave node in the same way as <c>start/1,2,3</c>, @@ -193,7 +193,7 @@ slave:start(H, Name, Arg).</code> </func> <func> - <name name="stop" arity="1"/> + <name name="stop" arity="1" since=""/> <fsummary>Stop (kill) a node.</fsummary> <desc> <p>Stops (kills) a node.</p> diff --git a/lib/stdlib/doc/src/sofs.xml b/lib/stdlib/doc/src/sofs.xml index 4cf1984d46..a0759d2f52 100644 --- a/lib/stdlib/doc/src/sofs.xml +++ b/lib/stdlib/doc/src/sofs.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>sofs.xml</file> </header> - <module>sofs</module> + <module since="">sofs</module> <modulesummary>Functions for manipulating sets of sets.</modulesummary> <description> <p>This module provides operations on finite sets and @@ -456,8 +456,8 @@ fun(S) -> sofs:partition(1, S) end <funcs> <func> - <name name="a_function" arity="1"/> - <name name="a_function" arity="2"/> + <name name="a_function" arity="1" since=""/> + <name name="a_function" arity="2" since=""/> <fsummary>Create a function.</fsummary> <desc> <p>Creates a <seealso marker="#function">function</seealso>. @@ -470,7 +470,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="canonical_relation" arity="1"/> + <name name="canonical_relation" arity="1" since=""/> <fsummary>Return the canonical map.</fsummary> <desc> <p>Returns the binary relation containing the elements @@ -490,7 +490,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="composite" arity="2"/> + <name name="composite" arity="2" since=""/> <fsummary>Return the composite of two functions.</fsummary> <desc> <p>Returns the <seealso marker="#composite">composite</seealso> of @@ -506,7 +506,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="constant_function" arity="2"/> + <name name="constant_function" arity="2" since=""/> <fsummary>Create the function that maps each element of a set onto another set.</fsummary> <desc> @@ -522,7 +522,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="converse" arity="1"/> + <name name="converse" arity="1" since=""/> <fsummary>Return the converse of a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#converse">converse</seealso> @@ -536,7 +536,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="difference" arity="2"/> + <name name="difference" arity="2" since=""/> <fsummary>Return the difference of two sets.</fsummary> <desc> <p>Returns the <seealso marker="#difference">difference</seealso> of @@ -545,8 +545,8 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="digraph_to_family" arity="1"/> - <name name="digraph_to_family" arity="2"/> + <name name="digraph_to_family" arity="1" since=""/> + <name name="digraph_to_family" arity="2" since=""/> <fsummary>Create a family from a directed graph.</fsummary> <desc> <p>Creates a <seealso marker="#family">family</seealso> from @@ -565,7 +565,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="domain" arity="1"/> + <name name="domain" arity="1" since=""/> <fsummary>Return the domain of a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#domain">domain</seealso> of @@ -579,7 +579,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="drestriction" arity="2"/> + <name name="drestriction" arity="2" since=""/> <fsummary>Return a restriction of a binary relation.</fsummary> <desc> <p>Returns the difference between the binary relation @@ -598,7 +598,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="drestriction" arity="3"/> + <name name="drestriction" arity="3" since=""/> <fsummary>Return a restriction of a relation.</fsummary> <desc> <p>Returns a subset of <c><anno>Set1</anno></c> containing those @@ -618,7 +618,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="empty_set" arity="0"/> + <name name="empty_set" arity="0" since=""/> <fsummary>Return the untyped empty set.</fsummary> <desc> <p>Returns the <seealso marker="#sets_definition">untyped empty @@ -628,7 +628,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="extension" arity="3"/> + <name name="extension" arity="3" since=""/> <fsummary>Extend the domain of a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#extension">extension</seealso> of @@ -648,8 +648,8 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family" arity="1"/> - <name name="family" arity="2"/> + <name name="family" arity="1" since=""/> + <name name="family" arity="2" since=""/> <fsummary>Create a family of subsets.</fsummary> <desc> <p>Creates a <seealso marker="#family">family of subsets</seealso>. @@ -662,7 +662,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_difference" arity="2"/> + <name name="family_difference" arity="2" since=""/> <fsummary>Return the difference of two families.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c> @@ -683,7 +683,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_domain" arity="1"/> + <name name="family_domain" arity="1" since=""/> <fsummary>Return a family of domains.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> is @@ -704,7 +704,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_field" arity="1"/> + <name name="family_field" arity="1" since=""/> <fsummary>Return a family of fields.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> is @@ -728,7 +728,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_intersection" arity="1"/> + <name name="family_intersection" arity="1" since=""/> <fsummary>Return the intersection of a family of sets of sets.</fsummary> <desc> @@ -752,7 +752,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_intersection" arity="2"/> + <name name="family_intersection" arity="2" since=""/> <fsummary>Return the intersection of two families.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c> @@ -772,7 +772,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_projection" arity="2"/> + <name name="family_projection" arity="2" since=""/> <fsummary>Return a family of modified subsets.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> is @@ -791,7 +791,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_range" arity="1"/> + <name name="family_range" arity="1" since=""/> <fsummary>Return a family of ranges.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> is @@ -812,7 +812,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_specification" arity="2"/> + <name name="family_specification" arity="2" since=""/> <fsummary>Select a subset of a family using a predicate.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> is @@ -837,8 +837,8 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_to_digraph" arity="1"/> - <name name="family_to_digraph" arity="2"/> + <name name="family_to_digraph" arity="1" since=""/> + <name name="family_to_digraph" arity="2" since=""/> <fsummary>Create a directed graph from a family.</fsummary> <desc> <p>Creates a directed graph from @@ -863,7 +863,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_to_relation" arity="1"/> + <name name="family_to_relation" arity="1" since=""/> <fsummary>Create a binary relation from a family.</fsummary> <desc> <p>If <c><anno>Family</anno></c> is @@ -881,7 +881,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_union" arity="1"/> + <name name="family_union" arity="1" since=""/> <fsummary>Return the union of a family of sets of sets.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> is @@ -904,7 +904,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="family_union" arity="2"/> + <name name="family_union" arity="2" since=""/> <fsummary>Return the union of two families.</fsummary> <desc> <p>If <c><anno>Family1</anno></c> and <c><anno>Family2</anno></c> @@ -926,7 +926,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="field" arity="1"/> + <name name="field" arity="1" since=""/> <fsummary>Return the field of a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#field">field</seealso> of the @@ -942,7 +942,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="from_external" arity="2"/> + <name name="from_external" arity="2" since=""/> <fsummary>Create a set.</fsummary> <desc> <p>Creates a set from the <seealso marker="#external_set">external @@ -955,7 +955,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="from_sets" arity="1" clause_i="1"/> + <name name="from_sets" arity="1" clause_i="1" since=""/> <fsummary>Create a set out of a list of sets.</fsummary> <desc> <p>Returns the <seealso marker="#sets_definition">unordered @@ -971,7 +971,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="from_sets" arity="1" clause_i="2"/> + <name name="from_sets" arity="1" clause_i="2" since=""/> <fsummary>Create an ordered set out of a tuple of sets.</fsummary> <desc> <p>Returns the <seealso marker="#sets_definition">ordered @@ -981,8 +981,8 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="from_term" arity="1"/> - <name name="from_term" arity="2"/> + <name name="from_term" arity="1" since=""/> + <name name="from_term" arity="2" since=""/> <fsummary>Create a set.</fsummary> <desc> <p><marker id="from_term"></marker>Creates an element @@ -1031,7 +1031,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="image" arity="2"/> + <name name="image" arity="2" since=""/> <fsummary>Return the image of a set under a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#image">image</seealso> of @@ -1047,7 +1047,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="intersection" arity="1"/> + <name name="intersection" arity="1" since=""/> <fsummary>Return the intersection of a set of sets.</fsummary> <desc> <p>Returns @@ -1059,7 +1059,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="intersection" arity="2"/> + <name name="intersection" arity="2" since=""/> <fsummary>Return the intersection of two sets.</fsummary> <desc> <p>Returns @@ -1069,7 +1069,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="intersection_of_family" arity="1"/> + <name name="intersection_of_family" arity="1" since=""/> <fsummary>Return the intersection of a family.</fsummary> <desc> <p>Returns the intersection of @@ -1086,7 +1086,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="inverse" arity="1"/> + <name name="inverse" arity="1" since=""/> <fsummary>Return the inverse of a function.</fsummary> <desc> <p>Returns the <seealso marker="#inverse">inverse</seealso> @@ -1100,7 +1100,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="inverse_image" arity="2"/> + <name name="inverse_image" arity="2" since=""/> <fsummary>Return the inverse image of a set under a binary relation.</fsummary> <desc> @@ -1117,7 +1117,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="is_a_function" arity="1"/> + <name name="is_a_function" arity="1" since=""/> <fsummary>Test for a function.</fsummary> <desc> <p>Returns <c>true</c> if the binary relation <c><anno>BinRel</anno></c> @@ -1127,7 +1127,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="is_disjoint" arity="2"/> + <name name="is_disjoint" arity="2" since=""/> <fsummary>Test for disjoint sets.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set1</anno></c> @@ -1138,7 +1138,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="is_empty_set" arity="1"/> + <name name="is_empty_set" arity="1" since=""/> <fsummary>Test for an empty set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is an empty @@ -1147,7 +1147,7 @@ fun(S) -> sofs:partition(1, S) end </func> <func> - <name name="is_equal" arity="2"/> + <name name="is_equal" arity="2" since=""/> <fsummary>Test two sets for equality.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>AnySet1</anno></c> @@ -1164,7 +1164,7 @@ true</pre> </func> <func> - <name name="is_set" arity="1"/> + <name name="is_set" arity="1" since=""/> <fsummary>Test for an unordered set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>AnySet</anno></c> is @@ -1175,7 +1175,7 @@ true</pre> </func> <func> - <name name="is_sofs_set" arity="1"/> + <name name="is_sofs_set" arity="1" since=""/> <fsummary>Test for an unordered set.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Term</anno></c> is @@ -1185,7 +1185,7 @@ true</pre> </func> <func> - <name name="is_subset" arity="2"/> + <name name="is_subset" arity="2" since=""/> <fsummary>Test two sets for subset.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>Set1</anno></c> is @@ -1195,7 +1195,7 @@ true</pre> </func> <func> - <name name="is_type" arity="1"/> + <name name="is_type" arity="1" since=""/> <fsummary>Test for a type.</fsummary> <desc> <p>Returns <c>true</c> if term <c><anno>Term</anno></c> is @@ -1204,7 +1204,7 @@ true</pre> </func> <func> - <name name="join" arity="4"/> + <name name="join" arity="4" since=""/> <fsummary>Return the join of two relations.</fsummary> <desc> <p>Returns the <seealso marker="#natural_join">natural @@ -1221,7 +1221,7 @@ true</pre> </func> <func> - <name name="multiple_relative_product" arity="2"/> + <name name="multiple_relative_product" arity="2" since=""/> <fsummary>Return the multiple relative product of a tuple of binary relations and a relation.</fsummary> <desc> @@ -1242,7 +1242,7 @@ true</pre> </func> <func> - <name name="no_elements" arity="1"/> + <name name="no_elements" arity="1" since=""/> <fsummary>Return the number of elements of a set.</fsummary> <desc> <p>Returns the number of elements of the ordered or unordered @@ -1251,7 +1251,7 @@ true</pre> </func> <func> - <name name="partition" arity="1"/> + <name name="partition" arity="1" since=""/> <fsummary>Return the coarsest partition given a set of sets.</fsummary> <desc> <p>Returns the <seealso marker="#partition">partition</seealso> of @@ -1268,7 +1268,7 @@ true</pre> </func> <func> - <name name="partition" arity="2"/> + <name name="partition" arity="2" since=""/> <fsummary>Return a partition of a set.</fsummary> <desc> <p>Returns the <seealso marker="#partition">partition</seealso> of @@ -1284,7 +1284,7 @@ true</pre> </func> <func> - <name name="partition" arity="3"/> + <name name="partition" arity="3" since=""/> <fsummary>Return a partition of a set.</fsummary> <desc> <p>Returns a pair of sets that, regarded as constituting a @@ -1307,7 +1307,7 @@ true</pre> </func> <func> - <name name="partition_family" arity="2"/> + <name name="partition_family" arity="2" since=""/> <fsummary>Return a family indexing a partition.</fsummary> <desc> <p>Returns <seealso marker="#family">family</seealso> @@ -1328,7 +1328,7 @@ true</pre> </func> <func> - <name name="product" arity="1"/> + <name name="product" arity="1" since=""/> <fsummary>Return the Cartesian product of a tuple of sets.</fsummary> <desc> <p>Returns the <seealso marker="#Cartesian_product_tuple">Cartesian @@ -1347,7 +1347,7 @@ true</pre> </func> <func> - <name name="product" arity="2"/> + <name name="product" arity="2" since=""/> <fsummary>Return the Cartesian product of two sets.</fsummary> <desc> <p>Returns the <seealso marker="#Cartesian_product">Cartesian @@ -1365,7 +1365,7 @@ true</pre> </func> <func> - <name name="projection" arity="2"/> + <name name="projection" arity="2" since=""/> <fsummary>Return a set of substituted elements.</fsummary> <desc> <p>Returns the set created by substituting each element of @@ -1384,7 +1384,7 @@ true</pre> </func> <func> - <name name="range" arity="1"/> + <name name="range" arity="1" since=""/> <fsummary>Return the range of a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#range">range</seealso> of the @@ -1398,8 +1398,8 @@ true</pre> </func> <func> - <name name="relation" arity="1"/> - <name name="relation" arity="2"/> + <name name="relation" arity="1" since=""/> + <name name="relation" arity="2" since=""/> <fsummary>Create a relation.</fsummary> <desc> <p>Creates a <seealso marker="#relation">relation</seealso>. @@ -1417,7 +1417,7 @@ true</pre> </func> <func> - <name name="relation_to_family" arity="1"/> + <name name="relation_to_family" arity="1" since=""/> <fsummary>Create a family from a binary relation.</fsummary> <desc> <p>Returns <seealso marker="#family">family</seealso> @@ -1435,8 +1435,8 @@ true</pre> </func> <func> - <name name="relative_product" arity="1"/> - <name name="relative_product" arity="2" clause_i="1"/> + <name name="relative_product" arity="1" since=""/> + <name name="relative_product" arity="2" clause_i="1" since=""/> <fsummary>Return the relative product of a list of binary relations and a binary relation.</fsummary> <desc> @@ -1466,7 +1466,7 @@ true</pre> </func> <func> - <name name="relative_product" arity="2" clause_i="2"/> + <name name="relative_product" arity="2" clause_i="2" since=""/> <fsummary>Return the relative product of two binary relations.</fsummary> <desc> @@ -1477,7 +1477,7 @@ true</pre> </func> <func> - <name name="relative_product1" arity="2"/> + <name name="relative_product1" arity="2" since=""/> <fsummary>Return the relative_product of two binary relations.</fsummary> <desc> @@ -1498,7 +1498,7 @@ true</pre> </func> <func> - <name name="restriction" arity="2"/> + <name name="restriction" arity="2" since=""/> <fsummary>Return a restriction of a binary relation.</fsummary> <desc> <p>Returns the <seealso marker="#restriction">restriction</seealso> of @@ -1514,7 +1514,7 @@ true</pre> </func> <func> - <name name="restriction" arity="3"/> + <name name="restriction" arity="3" since=""/> <fsummary>Return a restriction of a set.</fsummary> <desc> <p>Returns a subset of <c><anno>Set1</anno></c> containing those @@ -1530,8 +1530,8 @@ true</pre> </func> <func> - <name name="set" arity="1"/> - <name name="set" arity="2"/> + <name name="set" arity="1" since=""/> + <name name="set" arity="2" since=""/> <fsummary>Create a set of atoms or any type of sets.</fsummary> <desc> <p>Creates an <seealso marker="#sets_definition">unordered @@ -1543,7 +1543,7 @@ true</pre> </func> <func> - <name name="specification" arity="2"/> + <name name="specification" arity="2" since=""/> <fsummary>Select a subset using a predicate.</fsummary> <desc> <p>Returns the set containing every element @@ -1564,7 +1564,7 @@ true</pre> </func> <func> - <name name="strict_relation" arity="1"/> + <name name="strict_relation" arity="1" since=""/> <fsummary>Return the strict relation corresponding to a given relation.</fsummary> <desc> @@ -1580,7 +1580,7 @@ true</pre> </func> <func> - <name name="substitution" arity="2"/> + <name name="substitution" arity="2" since=""/> <fsummary>Return a function with a given set as domain.</fsummary> <desc> <p>Returns a function, the domain of which @@ -1629,7 +1629,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="symdiff" arity="2"/> + <name name="symdiff" arity="2" since=""/> <fsummary>Return the symmetric difference of two sets.</fsummary> <desc> <p>Returns the <seealso marker="#symmetric_difference">symmetric @@ -1645,7 +1645,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="symmetric_partition" arity="2"/> + <name name="symmetric_partition" arity="2" since=""/> <fsummary>Return a partition of two sets.</fsummary> <desc> <p>Returns a triple of sets:</p> @@ -1666,7 +1666,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="to_external" arity="1"/> + <name name="to_external" arity="1" since=""/> <fsummary>Return the elements of a set.</fsummary> <desc> <p>Returns the <seealso marker="#external_set">external @@ -1675,7 +1675,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="to_sets" arity="1"/> + <name name="to_sets" arity="1" since=""/> <fsummary>Return a list or a tuple of the elements of a set.</fsummary> <desc> <p>Returns the elements of the ordered set <c><anno>ASet</anno></c> @@ -1686,7 +1686,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="type" arity="1"/> + <name name="type" arity="1" since=""/> <fsummary>Return the type of a set.</fsummary> <desc> <p>Returns the <seealso marker="#type">type</seealso> of an @@ -1695,7 +1695,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="union" arity="1"/> + <name name="union" arity="1" since=""/> <fsummary>Return the union of a set of sets.</fsummary> <desc> <p>Returns the <seealso marker="#union_n">union</seealso> of the @@ -1704,7 +1704,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="union" arity="2"/> + <name name="union" arity="2" since=""/> <fsummary>Return the union of two sets.</fsummary> <desc> <p>Returns the <seealso marker="#union">union</seealso> of @@ -1713,7 +1713,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="union_of_family" arity="1"/> + <name name="union_of_family" arity="1" since=""/> <fsummary>Return the union of a family.</fsummary> <desc> <p>Returns the union of <seealso marker="#family">family</seealso> @@ -1727,7 +1727,7 @@ images2(SetOfSets, BinRel) -> </func> <func> - <name name="weak_relation" arity="1"/> + <name name="weak_relation" arity="1" since=""/> <fsummary>Return the weak relation corresponding to a given relation.</fsummary> <desc> diff --git a/lib/stdlib/doc/src/string.xml b/lib/stdlib/doc/src/string.xml index 3348464eba..d102191a57 100644 --- a/lib/stdlib/doc/src/string.xml +++ b/lib/stdlib/doc/src/string.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>string.xml</file> </header> - <module>string</module> + <module since="">string</module> <modulesummary>String processing functions.</modulesummary> <description> <p>This module provides functions for string processing.</p> @@ -130,7 +130,7 @@ <funcs> <func> - <name name="casefold" arity="1"/> + <name name="casefold" arity="1" since="OTP 20.0"/> <fsummary>Convert a string to a comparable string.</fsummary> <desc> <p> @@ -147,7 +147,7 @@ </func> <func> - <name name="chomp" arity="1"/> + <name name="chomp" arity="1" since="OTP 20.0"/> <fsummary>Remove trailing end of line control characters.</fsummary> <desc> <p> @@ -164,9 +164,9 @@ </func> <func> - <name name="equal" arity="2"/> - <name name="equal" arity="3"/> - <name name="equal" arity="4"/> + <name name="equal" arity="2" since=""/> + <name name="equal" arity="3" since="OTP 20.0"/> + <name name="equal" arity="4" since="OTP 20.0"/> <fsummary>Test string equality.</fsummary> <desc> <p> @@ -201,8 +201,8 @@ true</pre> </func> <func> - <name name="find" arity="2"/> - <name name="find" arity="3"/> + <name name="find" arity="2" since="OTP 20.0"/> + <name name="find" arity="3" since="OTP 20.0"/> <fsummary>Find start of substring.</fsummary> <desc> <p> @@ -230,7 +230,7 @@ nomatch</pre> </func> <func> - <name name="is_empty" arity="1"/> + <name name="is_empty" arity="1" since="OTP 20.0"/> <fsummary>Check if the string is empty.</fsummary> <desc> <p>Returns <c>true</c> if <c><anno>String</anno></c> is the @@ -245,7 +245,7 @@ true</pre> </func> <func> - <name name="length" arity="1"/> + <name name="length" arity="1" since="OTP 20.0"/> <fsummary>Calculate length of the string.</fsummary> <desc> <p> @@ -261,7 +261,7 @@ true</pre> </func> <func> - <name name="lexemes" arity="2"/> + <name name="lexemes" arity="2" since="OTP 20.0"/> <fsummary>Split string into lexemes.</fsummary> <desc> <p> @@ -287,7 +287,7 @@ true</pre> </func> <func> - <name name="lowercase" arity="1"/> + <name name="lowercase" arity="1" since="OTP 20.0"/> <fsummary>Convert a string to lowercase</fsummary> <desc> <p> @@ -306,7 +306,7 @@ true</pre> </func> <func> - <name name="next_codepoint" arity="1"/> + <name name="next_codepoint" arity="1" since="OTP 20.0"/> <fsummary>Pick the first codepoint.</fsummary> <desc> <p> @@ -323,7 +323,7 @@ true</pre> </func> <func> - <name name="next_grapheme" arity="1"/> + <name name="next_grapheme" arity="1" since="OTP 20.0"/> <fsummary>Pick the first grapheme cluster.</fsummary> <desc> <p> @@ -340,7 +340,7 @@ true</pre> </func> <func> - <name name="nth_lexeme" arity="3"/> + <name name="nth_lexeme" arity="3" since="OTP 20.0"/> <fsummary>Pick the nth lexeme.</fsummary> <desc> <p>Returns lexeme number <c><anno>N</anno></c> in @@ -355,9 +355,9 @@ true</pre> </func> <func> - <name name="pad" arity="2"/> - <name name="pad" arity="3"/> - <name name="pad" arity="4"/> + <name name="pad" arity="2" since="OTP 20.0"/> + <name name="pad" arity="3" since="OTP 20.0"/> + <name name="pad" arity="4" since="OTP 20.0"/> <fsummary>Pad a string to given length.</fsummary> <desc> <p> @@ -381,7 +381,7 @@ true</pre> </func> <func> - <name name="prefix" arity="2"/> + <name name="prefix" arity="2" since="OTP 20.0"/> <fsummary>Remove prefix from string.</fsummary> <desc> <p> @@ -400,8 +400,8 @@ nomatch</pre> </func> <func> - <name name="replace" arity="3"/> - <name name="replace" arity="4"/> + <name name="replace" arity="3" since="OTP 20.0"/> + <name name="replace" arity="4" since="OTP 20.0"/> <fsummary>Replace a pattern in string.</fsummary> <desc> <p> @@ -423,7 +423,7 @@ nomatch</pre> </func> <func> - <name name="reverse" arity="1"/> + <name name="reverse" arity="1" since="OTP 20.0"/> <fsummary>Reverses a string</fsummary> <desc> <p> @@ -439,8 +439,8 @@ ÖÄÅ</pre> </func> <func> - <name name="slice" arity="2"/> - <name name="slice" arity="3"/> + <name name="slice" arity="2" since="OTP 20.0"/> + <name name="slice" arity="3" since="OTP 20.0"/> <fsummary>Extract a part of string</fsummary> <desc> <p>Returns a substring of <c><anno>String</anno></c> of @@ -459,8 +459,8 @@ ÖÄÅ</pre> </func> <func> - <name name="split" arity="2"/> - <name name="split" arity="3"/> + <name name="split" arity="2" since="OTP 20.0"/> + <name name="split" arity="3" since="OTP 20.0"/> <fsummary>Split a string into substrings.</fsummary> <desc> <p> @@ -482,9 +482,9 @@ ÖÄÅ</pre> </func> <func> - <name name="take" arity="2"/> - <name name="take" arity="3"/> - <name name="take" arity="4"/> + <name name="take" arity="2" since="OTP 20.0"/> + <name name="take" arity="3" since="OTP 20.0"/> + <name name="take" arity="4" since="OTP 20.0"/> <fsummary>Take leading or trailing parts.</fsummary> <desc> <p>Takes characters from <c><anno>String</anno></c> as long as @@ -508,7 +508,7 @@ ÖÄÅ</pre> </func> <func> - <name name="titlecase" arity="1"/> + <name name="titlecase" arity="1" since="OTP 20.0"/> <fsummary>Convert a string to titlecase.</fsummary> <desc> <p> @@ -522,7 +522,7 @@ ÖÄÅ</pre> </func> <func> - <name name="to_float" arity="1"/> + <name name="to_float" arity="1" since=""/> <fsummary>Return a float whose text representation is the integers (ASCII values) of a string.</fsummary> <desc> @@ -544,7 +544,7 @@ ÖÄÅ</pre> </func> <func> - <name name="to_integer" arity="1"/> + <name name="to_integer" arity="1" since=""/> <fsummary>Return an integer whose text representation is the integers (ASCII values) of a string.</fsummary> <desc> @@ -566,7 +566,7 @@ ÖÄÅ</pre> </func> <func> - <name name="to_graphemes" arity="1"/> + <name name="to_graphemes" arity="1" since="OTP 20.0"/> <fsummary>Convert a string to a list of grapheme clusters.</fsummary> <desc> <p> @@ -582,9 +582,9 @@ ÖÄÅ</pre> </func> <func> - <name name="trim" arity="1"/> - <name name="trim" arity="2"/> - <name name="trim" arity="3"/> + <name name="trim" arity="1" since="OTP 20.0"/> + <name name="trim" arity="2" since="OTP 20.0"/> + <name name="trim" arity="3" since="OTP 20.0"/> <fsummary>Trim leading or trailing, or both, characters.</fsummary> <desc> <p> @@ -616,7 +616,7 @@ ÖÄÅ</pre> </func> <func> - <name name="uppercase" arity="1"/> + <name name="uppercase" arity="1" since="OTP 20.0"/> <fsummary>Convert a string to uppercase.</fsummary> <desc> <p> @@ -649,8 +649,8 @@ ÖÄÅ</pre> <funcs> <func> - <name name="centre" arity="2"/> - <name name="centre" arity="3"/> + <name name="centre" arity="2" since=""/> + <name name="centre" arity="3" since=""/> <fsummary>Center a string.</fsummary> <desc> <p>Returns a string, where <c><anno>String</anno></c> is centered in the @@ -664,8 +664,8 @@ ÖÄÅ</pre> </func> <func> - <name name="chars" arity="2"/> - <name name="chars" arity="3"/> + <name name="chars" arity="2" since=""/> + <name name="chars" arity="3" since=""/> <fsummary>Return a string consisting of numbers of characters.</fsummary> <desc> <p>Returns a string consisting of <c><anno>Number</anno></c> characters @@ -678,7 +678,7 @@ ÖÄÅ</pre> </func> <func> - <name name="chr" arity="2"/> + <name name="chr" arity="2" since=""/> <fsummary>Return the index of the first occurrence of a character in a string.</fsummary> <desc> @@ -692,7 +692,7 @@ ÖÄÅ</pre> </func> <func> - <name name="concat" arity="2"/> + <name name="concat" arity="2" since=""/> <fsummary>Concatenate two strings.</fsummary> <desc> <p>Concatenates <c><anno>String1</anno></c> and @@ -712,7 +712,7 @@ ÖÄÅ</pre> </func> <func> - <name name="copies" arity="2"/> + <name name="copies" arity="2" since=""/> <fsummary>Copy a string.</fsummary> <desc> <p>Returns a string containing <c><anno>String</anno></c> repeated @@ -724,7 +724,7 @@ ÖÄÅ</pre> </func> <func> - <name name="cspan" arity="2"/> + <name name="cspan" arity="2" since=""/> <fsummary>Span characters at start of a string.</fsummary> <desc> <p>Returns the length of the maximum initial segment of @@ -741,7 +741,7 @@ ÖÄÅ</pre> </func> <func> - <name name="join" arity="2"/> + <name name="join" arity="2" since=""/> <fsummary>Join a list of strings with separator.</fsummary> <desc> <p>Returns a string with the elements of <c><anno>StringList</anno></c> @@ -757,8 +757,8 @@ ÖÄÅ</pre> </func> <func> - <name name="left" arity="2"/> - <name name="left" arity="3"/> + <name name="left" arity="2" since=""/> + <name name="left" arity="3" since=""/> <fsummary>Adjust left end of a string.</fsummary> <desc> <p>Returns <c><anno>String</anno></c> with the length adjusted in @@ -778,7 +778,7 @@ ÖÄÅ</pre> </func> <func> - <name name="len" arity="1"/> + <name name="len" arity="1" since=""/> <fsummary>Return the length of a string.</fsummary> <desc> <p>Returns the number of characters in <c><anno>String</anno></c>.</p> @@ -789,7 +789,7 @@ ÖÄÅ</pre> </func> <func> - <name name="rchr" arity="2"/> + <name name="rchr" arity="2" since=""/> <fsummary>Return the index of the last occurrence of a character in a string.</fsummary> <desc> @@ -803,8 +803,8 @@ ÖÄÅ</pre> </func> <func> - <name name="right" arity="2"/> - <name name="right" arity="3"/> + <name name="right" arity="2" since=""/> + <name name="right" arity="3" since=""/> <fsummary>Adjust right end of a string.</fsummary> <desc> <p>Returns <c><anno>String</anno></c> with the length adjusted in @@ -823,7 +823,7 @@ ÖÄÅ</pre> </func> <func> - <name name="rstr" arity="2"/> + <name name="rstr" arity="2" since=""/> <fsummary>Find the index of a substring.</fsummary> <desc> <p>Returns the position where the last occurrence of @@ -841,7 +841,7 @@ ÖÄÅ</pre> </func> <func> - <name name="span" arity="2"/> + <name name="span" arity="2" since=""/> <fsummary>Span characters at start of a string.</fsummary> <desc> <p>Returns the length of the maximum initial segment of @@ -858,7 +858,7 @@ ÖÄÅ</pre> </func> <func> - <name name="str" arity="2"/> + <name name="str" arity="2" since=""/> <fsummary>Find the index of a substring.</fsummary> <desc> <p>Returns the position where the first occurrence of @@ -876,9 +876,9 @@ ÖÄÅ</pre> </func> <func> - <name name="strip" arity="1"/> - <name name="strip" arity="2"/> - <name name="strip" arity="3"/> + <name name="strip" arity="1" since=""/> + <name name="strip" arity="2" since=""/> + <name name="strip" arity="3" since=""/> <fsummary>Strip leading or trailing characters.</fsummary> <desc> <p>Returns a string, where leading or trailing, or both, blanks or a @@ -898,8 +898,8 @@ ÖÄÅ</pre> </func> <func> - <name name="sub_string" arity="2"/> - <name name="sub_string" arity="3"/> + <name name="sub_string" arity="2" since=""/> + <name name="sub_string" arity="3" since=""/> <fsummary>Extract a substring.</fsummary> <desc> <p>Returns a substring of <c><anno>String</anno></c>, starting at @@ -916,8 +916,8 @@ sub_string("Hello World", 4, 8). </func> <func> - <name name="substr" arity="2"/> - <name name="substr" arity="3"/> + <name name="substr" arity="2" since=""/> + <name name="substr" arity="3" since=""/> <fsummary>Return a substring of a string.</fsummary> <desc> <p>Returns a substring of <c><anno>String</anno></c>, starting at @@ -934,8 +934,8 @@ sub_string("Hello World", 4, 8). </func> <func> - <name name="sub_word" arity="2"/> - <name name="sub_word" arity="3"/> + <name name="sub_word" arity="2" since=""/> + <name name="sub_word" arity="3" since=""/> <fsummary>Extract subword.</fsummary> <desc> <p>Returns the word in position <c><anno>Number</anno></c> of @@ -952,10 +952,10 @@ sub_string("Hello World", 4, 8). </func> <func> - <name name="to_lower" arity="1" clause_i="1"/> - <name name="to_lower" arity="1" clause_i="2"/> - <name name="to_upper" arity="1" clause_i="1"/> - <name name="to_upper" arity="1" clause_i="2"/> + <name name="to_lower" arity="1" clause_i="1" since=""/> + <name name="to_lower" arity="1" clause_i="2" since=""/> + <name name="to_upper" arity="1" clause_i="1" since=""/> + <name name="to_upper" arity="1" clause_i="2" since=""/> <fsummary>Convert case of string (ISO/IEC 8859-1).</fsummary> <type variable="String" name_i="1"/> <type variable="Result" name_i="1"/> @@ -974,7 +974,7 @@ sub_string("Hello World", 4, 8). </func> <func> - <name name="tokens" arity="2"/> + <name name="tokens" arity="2" since=""/> <fsummary>Split string into tokens.</fsummary> <desc> <p>Returns a list of tokens in <c><anno>String</anno></c>, separated @@ -994,8 +994,8 @@ sub_string("Hello World", 4, 8). </func> <func> - <name name="words" arity="1"/> - <name name="words" arity="2"/> + <name name="words" arity="1" since=""/> + <name name="words" arity="2" since=""/> <fsummary>Count blank separated words.</fsummary> <desc> <p>Returns the number of words in <c><anno>String</anno></c>, separated diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml index 5fd5760499..f15b1a2dd3 100644 --- a/lib/stdlib/doc/src/supervisor.xml +++ b/lib/stdlib/doc/src/supervisor.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>supervisor</module> + <module since="">supervisor</module> <modulesummary>Generic supervisor behavior.</modulesummary> <description> <p>This behavior module provides a supervisor, a process that @@ -318,7 +318,7 @@ child_spec() = #{id => child_id(), % mandatory <funcs> <func> - <name name="check_childspecs" arity="1"/> + <name name="check_childspecs" arity="1" since=""/> <fsummary>Check if children specifications are syntactically correct. </fsummary> <desc> @@ -329,7 +329,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="count_children" arity="1"/> + <name name="count_children" arity="1" since="OTP R13B04"/> <fsummary>Return counts for the number of child specifications, active children, supervisors, and workers.</fsummary> <desc> @@ -366,7 +366,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="delete_child" arity="2"/> + <name name="delete_child" arity="2" since=""/> <fsummary>Delete a child specification from a supervisor.</fsummary> <desc> <p>Tells supervisor <c><anno>SupRef</anno></c> to delete the child @@ -387,7 +387,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="get_childspec" arity="2"/> + <name name="get_childspec" arity="2" since="OTP 18.0"/> <fsummary>Return the child specification map for the specified child.</fsummary> <desc> @@ -400,7 +400,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="restart_child" arity="2"/> + <name name="restart_child" arity="2" since=""/> <fsummary>Restart a terminated child process belonging to a supervisor. </fsummary> <desc> @@ -436,7 +436,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="start_child" arity="2"/> + <name name="start_child" arity="2" since=""/> <fsummary>Dynamically add a child process to a supervisor.</fsummary> <type name="startchild_ret"/> <type name="startchild_err"/> @@ -503,8 +503,8 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="start_link" arity="2"/> - <name name="start_link" arity="3"/> + <name name="start_link" arity="2" since=""/> + <name name="start_link" arity="3" since=""/> <fsummary>Create a supervisor process.</fsummary> <type name="startlink_ret"/> <type name="startlink_err"/> @@ -584,7 +584,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="terminate_child" arity="2"/> + <name name="terminate_child" arity="2" since=""/> <fsummary>Terminate a child process belonging to a supervisor.</fsummary> <desc> <p>Tells supervisor <c><anno>SupRef</anno></c> to terminate the @@ -621,7 +621,7 @@ child_spec() = #{id => child_id(), % mandatory </func> <func> - <name name="which_children" arity="1"/> + <name name="which_children" arity="1" since=""/> <fsummary>Return information about all children specifications and child processes belonging to a supervisor.</fsummary> <desc> @@ -666,7 +666,7 @@ child_spec() = #{id => child_id(), % mandatory <funcs> <func> - <name>Module:init(Args) -> Result</name> + <name since="">Module:init(Args) -> Result</name> <fsummary>Return a supervisor specification.</fsummary> <type> <v>Args = term()</v> diff --git a/lib/stdlib/doc/src/supervisor_bridge.xml b/lib/stdlib/doc/src/supervisor_bridge.xml index c4c1b37548..ee5d97fea1 100644 --- a/lib/stdlib/doc/src/supervisor_bridge.xml +++ b/lib/stdlib/doc/src/supervisor_bridge.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>supervisor_bridge</module> + <module since="">supervisor_bridge</module> <modulesummary>Generic supervisor bridge behavior.</modulesummary> <description> <p>This behavior module provides a supervisor bridge, a process @@ -57,8 +57,8 @@ <funcs> <func> - <name name="start_link" arity="2"/> - <name name="start_link" arity="3"/> + <name name="start_link" arity="2" since=""/> + <name name="start_link" arity="3" since=""/> <fsummary>Create a supervisor bridge process.</fsummary> <desc> <p>Creates a supervisor bridge process, linked to the calling process, @@ -133,7 +133,7 @@ <funcs> <func> - <name>Module:init(Args) -> Result</name> + <name since="">Module:init(Args) -> Result</name> <fsummary>Initialize process and start subsystem.</fsummary> <type> <v>Args = term()</v> @@ -164,7 +164,7 @@ </func> <func> - <name>Module:terminate(Reason, State)</name> + <name since="">Module:terminate(Reason, State)</name> <fsummary>Clean up and stop subsystem.</fsummary> <type> <v>Reason = shutdown | term()</v> diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml index 9fe816e33a..c5075f31c5 100644 --- a/lib/stdlib/doc/src/sys.xml +++ b/lib/stdlib/doc/src/sys.xml @@ -32,7 +32,7 @@ <rev></rev> <file>sys.xml</file> </header> - <module>sys</module> + <module since="">sys</module> <modulesummary>A functional interface to system messages.</modulesummary> <description> <p>This module contains functions for sending system messages used by @@ -114,6 +114,154 @@ </datatype> <datatype> <name name="system_event"/> + <desc> + <taglist> + <tag><c>{in,<anno>Msg</anno>}</c></tag> + <item> + <p> + Is produced by <c>gen_server</c> and <c>gen_event</c> + when the message <c>Msg</c> arrives. + </p> + </item> + <tag><c>{in,<anno>Msg</anno>,<anno>State</anno>}</c></tag> + <item> + <p> + Is produced by <c>gen_statem</c> + when the message <c>Msg</c> arrives in state <c>State</c>. + </p> + <p> + For <c>gen_statem</c> the <c><anno>Msg</anno></c> term is + an <c>{EventType,EventContent}</c> tuple. + </p> + </item> + <tag><c>{out,<anno>Msg</anno>,<anno>To</anno>}</c></tag> + <item> + <p> + Is produced by <c>gen_statem</c> when the reply <c>Msg</c> + is sent back to <c>To</c> by returning + a <c>{reply,To,Msg}</c> action from the callback module. + </p> + <p> + <c><anno>To</anno></c> is of the same type + as the first argument to <c>gen_statem:reply/2</c>. + </p> + </item> + <tag> + <c>{out,<anno>Msg</anno>,<anno>To</anno>,<anno>State</anno>}</c> + </tag> + <item> + <p> + Is produced by <c>gen_server</c> + when the reply <c><anno>Msg</anno></c> + is sent back to <c><anno>To</anno></c> + by returning a <c>{reply,...}</c> tuple + from the callback module. + </p> + <p> + <c><anno>To</anno></c> is of the same type + as the first argument to <c>gen_server:reply/2</c>. + </p> + <p> + <c><anno>State</anno></c> is the new server state. + </p> + </item> + <tag> + <c>{noreply,<anno>State</anno>}</c> + </tag> + <item> + <p> + Is produced by <c>gen_server</c> + when a <c>{noreply,...}</c> tuple is returned + from the callback module. + </p> + <p> + <c><anno>State</anno></c> is the new server state. + </p> + </item> + <tag> + <c>{continue,<anno>Continuation</anno>}</c> + </tag> + <item> + <p> + Is produced by <c>gen_server</c> + when a <c>{continue,<anno>Continuation</anno>}</c> + tuple is returned from the callback module. + </p> + </item> + <tag> + <c>{code_change,<anno>Event</anno>,<anno>State</anno>}</c> + </tag> + <item> + <p> + Is produced by <c>gen_statem</c> + when the message <c><anno>Event</anno></c> + arrives in state <c><anno>State</anno></c> + as the first event after a code change. + </p> + <p> + <c><anno>Event</anno></c> is + an <c>{EventType,EventContent}</c> tuple. + </p> + </item> + <tag> + <c> + {postpone,<anno>Event</anno>,<anno>State</anno>,<anno>NextState</anno>} + </c> + </tag> + <item> + <p> + Is produced by <c>gen_statem</c> + when the message <c><anno>Event</anno></c> + is postponed in state <c><anno>State</anno></c>. + <c><anno>NextState</anno></c> is the new state. + </p> + <p> + <c><anno>Event</anno></c> is + an <c>{EventType,EventContent}</c> tuple. + </p> + </item> + <tag> + <c> + {consume,<anno>Event</anno>,<anno>State</anno>,<anno>NextState</anno>} + </c> + </tag> + <item> + <p> + Is produced by <c>gen_statem</c> + when the message <c><anno>Event</anno></c> + is consumed in state <c><anno>State</anno></c>. + <c><anno>NextState</anno></c> is the new state. + </p> + <p> + <c><anno>Event</anno></c> is + an <c>{EventType,EventContent}</c> tuple. + </p> + </item> + <tag> + <c> + {enter,<anno>State</anno>} + </c> + </tag> + <item> + <p> + Is produced by <c>gen_statem</c> + when the first state <c><anno>State</anno></c> is entered. + </p> + </item> + <tag> + <c> + {terminate,<anno>Reason</anno>,<anno>State</anno>} + </c> + </tag> + <item> + <p> + Is produced by <c>gen_statem</c> + when it terminates with reason <c><anno>Reason</anno></c> + in state <c><anno>State</anno></c>. + </p> + </item> + </taglist> + </desc> </datatype> <datatype> <name name="dbg_opt"/> @@ -129,8 +277,8 @@ <funcs> <func> - <name name="change_code" arity="4"/> - <name name="change_code" arity="5"/> + <name name="change_code" arity="4" since=""/> + <name name="change_code" arity="5" since=""/> <fsummary>Send the code change system message to the process.</fsummary> <desc> <p>Tells the process to change code. The process must be @@ -143,8 +291,8 @@ </func> <func> - <name name="get_state" arity="1"/> - <name name="get_state" arity="2"/> + <name name="get_state" arity="1" since="OTP R16B01"/> + <name name="get_state" arity="2" since="OTP R16B01"/> <fsummary>Get the state of the process.</fsummary> <desc> <p>Gets the state of the process.</p> @@ -227,8 +375,8 @@ </func> <func> - <name name="get_status" arity="1"/> - <name name="get_status" arity="2"/> + <name name="get_status" arity="1" since=""/> + <name name="get_status" arity="2" since=""/> <fsummary>Get the status of the process.</fsummary> <desc> <p>Gets the status of the process.</p> @@ -265,8 +413,8 @@ </func> <func> - <name name="install" arity="2"/> - <name name="install" arity="3"/> + <name name="install" arity="2" since=""/> + <name name="install" arity="3" since=""/> <fsummary>Install a debug function in the process.</fsummary> <desc> <p>Enables installation of alternative debug functions. An example of @@ -283,8 +431,8 @@ </func> <func> - <name name="log" arity="2"/> - <name name="log" arity="3"/> + <name name="log" arity="2" since=""/> + <name name="log" arity="3" since=""/> <fsummary>Log system events in memory.</fsummary> <desc> <p>Turns the logging of system events on or off. If on, a @@ -302,8 +450,8 @@ </func> <func> - <name name="log_to_file" arity="2"/> - <name name="log_to_file" arity="3"/> + <name name="log_to_file" arity="2" since=""/> + <name name="log_to_file" arity="3" since=""/> <fsummary>Log system events to the specified file.</fsummary> <desc> <p>Enables or disables the logging of all system events in text @@ -315,8 +463,8 @@ </func> <func> - <name name="no_debug" arity="1"/> - <name name="no_debug" arity="2"/> + <name name="no_debug" arity="1" since=""/> + <name name="no_debug" arity="2" since=""/> <fsummary>Turn off debugging.</fsummary> <desc> <p>Turns off all debugging for the process. This includes @@ -327,8 +475,8 @@ </func> <func> - <name name="remove" arity="2"/> - <name name="remove" arity="3"/> + <name name="remove" arity="2" since=""/> + <name name="remove" arity="3" since=""/> <fsummary>Remove a debug function from the process.</fsummary> <desc> <p>Removes an installed debug function from the @@ -338,8 +486,8 @@ </func> <func> - <name name="replace_state" arity="2"/> - <name name="replace_state" arity="3"/> + <name name="replace_state" arity="2" since="OTP R16B01"/> + <name name="replace_state" arity="3" since="OTP R16B01"/> <fsummary>Replace the state of the process.</fsummary> <desc> <p>Replaces the state of the process, and returns the new state.</p> @@ -451,8 +599,8 @@ </func> <func> - <name name="resume" arity="1"/> - <name name="resume" arity="2"/> + <name name="resume" arity="1" since=""/> + <name name="resume" arity="2" since=""/> <fsummary>Resume a suspended process.</fsummary> <desc> <p>Resumes a suspended process.</p> @@ -460,8 +608,8 @@ </func> <func> - <name name="statistics" arity="2"/> - <name name="statistics" arity="3"/> + <name name="statistics" arity="2" since=""/> + <name name="statistics" arity="3" since=""/> <fsummary>Enable or disable the collections of statistics.</fsummary> <desc> <p>Enables or disables the collection of statistics. If @@ -471,8 +619,8 @@ </func> <func> - <name name="suspend" arity="1"/> - <name name="suspend" arity="2"/> + <name name="suspend" arity="1" since=""/> + <name name="suspend" arity="2" since=""/> <fsummary>Suspend the process.</fsummary> <desc> <p>Suspends the process. When the process is suspended, it @@ -482,8 +630,8 @@ </func> <func> - <name name="terminate" arity="2"/> - <name name="terminate" arity="3"/> + <name name="terminate" arity="2" since="OTP 18.0"/> + <name name="terminate" arity="3" since="OTP 18.0"/> <fsummary>Terminate the process.</fsummary> <desc> <p>Orders the process to terminate with the @@ -494,8 +642,8 @@ </func> <func> - <name name="trace" arity="2"/> - <name name="trace" arity="3"/> + <name name="trace" arity="2" since=""/> + <name name="trace" arity="3" since=""/> <fsummary>Print all system events on <c>standard_io</c>.</fsummary> <desc> <p>Prints all system events on <c>standard_io</c>. The events are @@ -518,7 +666,7 @@ <funcs> <func> - <name name="debug_options" arity="1"/> + <name name="debug_options" arity="1" since=""/> <fsummary>Convert a list of options to a debug structure.</fsummary> <desc> <p>Can be used by a process that initiates a debug @@ -529,9 +677,15 @@ </func> <func> - <name name="get_debug" arity="3"/> + <name name="get_debug" arity="3" since=""/> <fsummary>Get the data associated with a debug option.</fsummary> <desc> + <warning> + <p> + <c>get_debug/3</c> is deprecated since it returns + data of an internal type only useful for debugging. + </p> + </warning> <p>Gets the data associated with a debug option. <c><anno>Default</anno></c> is returned if <c><anno>Item</anno></c> is not found. Can be @@ -541,7 +695,7 @@ </func> <func> - <name name="handle_debug" arity="4"/> + <name name="handle_debug" arity="4" since=""/> <fsummary>Generate a system event.</fsummary> <desc> <p>This function is called by a process when it generates a @@ -556,7 +710,7 @@ </func> <func> - <name name="handle_system_msg" arity="6"/> + <name name="handle_system_msg" arity="6" since=""/> <fsummary>Take care of system messages.</fsummary> <desc> <p>This function is used by a process module to take care of system @@ -594,7 +748,7 @@ </func> <func> - <name name="print_log" arity="1"/> + <name name="print_log" arity="1" since=""/> <fsummary>Print the logged events in the debug structure.</fsummary> <desc> <p>Prints the logged system events in the debug structure, @@ -605,7 +759,19 @@ </func> <func> - <name>Module:system_code_change(Misc, Module, OldVsn, Extra) -> + <name name="get_log" arity="1" since="OTP-22.0"/> + <fsummary>Return the logged events in the debug structure.</fsummary> + <desc> + <p> + Returns the logged system events in the debug structure, + that is the last argument to + <seealso marker="#handle_debug/4"><c>handle_debug/4</c></seealso>. + </p> + </desc> + </func> + + <func> + <name since="">Module:system_code_change(Misc, Module, OldVsn, Extra) -> {ok, NMisc}</name> <fsummary>Called when the process is to perform a code change.</fsummary> <type> @@ -628,7 +794,7 @@ </func> <func> - <name>Module:system_continue(Parent, Debug, Misc) -> none()</name> + <name since="">Module:system_continue(Parent, Debug, Misc) -> none()</name> <fsummary>Called when the process is to continue its execution.</fsummary> <type> <v>Parent = pid()</v> @@ -644,7 +810,7 @@ </func> <func> - <name>Module:system_get_state(Misc) -> {ok, State}</name> + <name since="OTP 17.0">Module:system_get_state(Misc) -> {ok, State}</name> <fsummary>Called when the process is to return its current state. </fsummary> <type> @@ -661,7 +827,7 @@ </func> <func> - <name>Module:system_replace_state(StateFun, Misc) -> + <name since="OTP 17.0">Module:system_replace_state(StateFun, Misc) -> {ok, NState, NMisc}</name> <fsummary>Called when the process is to replace its current state. </fsummary> @@ -681,7 +847,7 @@ </func> <func> - <name>Module:system_terminate(Reason, Parent, Debug, Misc) -> none()</name> + <name since="">Module:system_terminate(Reason, Parent, Debug, Misc) -> none()</name> <fsummary>Called when the process is to terminate.</fsummary> <type> <v>Reason = term()</v> diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml index e913e33589..165eecfbb0 100644 --- a/lib/stdlib/doc/src/timer.xml +++ b/lib/stdlib/doc/src/timer.xml @@ -32,7 +32,7 @@ <rev>D</rev> <file>timer.xml</file> </header> - <module>timer</module> + <module since="">timer</module> <modulesummary>Timer functions.</modulesummary> <description> <p>This module provides useful functions related to time. Unless otherwise @@ -62,7 +62,7 @@ <funcs> <func> - <name name="apply_after" arity="4"/> + <name name="apply_after" arity="4" since=""/> <fsummary>Apply <c>Module:Function(Arguments)</c> after a specified <c>Time</c>.</fsummary> <desc> @@ -75,7 +75,7 @@ </func> <func> - <name name="apply_interval" arity="4"/> + <name name="apply_interval" arity="4" since=""/> <fsummary>Evaluate <c>Module:Function(Arguments)</c> repeatedly at intervals of <c>Time</c>.</fsummary> <desc> @@ -88,7 +88,7 @@ </func> <func> - <name name="cancel" arity="1"/> + <name name="cancel" arity="1" since=""/> <fsummary>Cancel a previously requested time-out identified by <c>TRef</c>.</fsummary> <desc> @@ -101,8 +101,8 @@ </func> <func> - <name name="exit_after" arity="2"/> - <name name="exit_after" arity="3"/> + <name name="exit_after" arity="2" since=""/> + <name name="exit_after" arity="3" since=""/> <fsummary>Send an exit signal with <c>Reason</c> after a specified <c>Time</c>.</fsummary> <desc> @@ -117,7 +117,7 @@ </func> <func> - <name name="hms" arity="3"/> + <name name="hms" arity="3" since=""/> <fsummary>Convert <c>Hours</c>+<c>Minutes</c>+<c>Seconds</c> to <c>Milliseconds</c>.</fsummary> <desc> @@ -127,7 +127,7 @@ </func> <func> - <name name="hours" arity="1"/> + <name name="hours" arity="1" since=""/> <fsummary>Convert <c>Hours</c> to <c>Milliseconds</c>.</fsummary> <desc> <p>Returns the number of milliseconds in <c><anno>Hours</anno></c>.</p> @@ -135,8 +135,8 @@ </func> <func> - <name name="kill_after" arity="1"/> - <name name="kill_after" arity="2"/> + <name name="kill_after" arity="1" since=""/> + <name name="kill_after" arity="2" since=""/> <fsummary>Send an exit signal with <c>Reason</c> after a specified <c>Time</c>.</fsummary> <desc> @@ -148,7 +148,7 @@ </func> <func> - <name name="minutes" arity="1"/> + <name name="minutes" arity="1" since=""/> <fsummary>Converts <c>Minutes</c> to <c>Milliseconds</c>.</fsummary> <desc> <p>Returns the number of milliseconds in @@ -157,7 +157,7 @@ </func> <func> - <name name="now_diff" arity="2"/> + <name name="now_diff" arity="2" since=""/> <fsummary>Calculate time difference between time stamps.</fsummary> <type_desc variable="Tdiff">In microseconds</type_desc> <desc> @@ -173,7 +173,7 @@ </func> <func> - <name name="seconds" arity="1"/> + <name name="seconds" arity="1" since=""/> <fsummary>Convert <c>Seconds</c> to <c>Milliseconds</c>.</fsummary> <desc> <p>Returns the number of milliseconds in @@ -182,8 +182,8 @@ </func> <func> - <name name="send_after" arity="2"/> - <name name="send_after" arity="3"/> + <name name="send_after" arity="2" since=""/> + <name name="send_after" arity="3" since=""/> <fsummary>Send <c>Message</c> to <c>Pid</c> after a specified <c>Time</c>.</fsummary> <desc> @@ -206,8 +206,8 @@ </func> <func> - <name name="send_interval" arity="2"/> - <name name="send_interval" arity="3"/> + <name name="send_interval" arity="2" since=""/> + <name name="send_interval" arity="3" since=""/> <fsummary>Send <c>Message</c> repeatedly at intervals of <c>Time</c>. </fsummary> <desc> @@ -231,7 +231,7 @@ </func> <func> - <name name="sleep" arity="1"/> + <name name="sleep" arity="1" since=""/> <fsummary>Suspend the calling process for <c>Time</c> milliseconds. </fsummary> <desc> @@ -244,7 +244,7 @@ </func> <func> - <name name="start" arity="0"/> + <name name="start" arity="0" since=""/> <fsummary>Start a global timer server (named <c>timer_server</c>). </fsummary> <desc> @@ -258,9 +258,9 @@ </func> <func> - <name name="tc" arity="1"/> - <name name="tc" arity="2"/> - <name name="tc" arity="3"/> + <name name="tc" arity="1" since="OTP R14B03"/> + <name name="tc" arity="2" since="OTP R14B"/> + <name name="tc" arity="3" since=""/> <fsummary>Measure the real time it takes to evaluate <c>apply(Module, Function, Arguments)</c> or <c>apply(Fun, Arguments)</c>.</fsummary> <type_desc variable="Time">In microseconds</type_desc> diff --git a/lib/stdlib/doc/src/unicode.xml b/lib/stdlib/doc/src/unicode.xml index d822aca89c..b7696a4b7e 100644 --- a/lib/stdlib/doc/src/unicode.xml +++ b/lib/stdlib/doc/src/unicode.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>unicode</module> + <module since="">unicode</module> <modulesummary>Functions for converting Unicode characters.</modulesummary> <description> <p>This module contains functions for converting between different character @@ -139,7 +139,7 @@ <funcs> <func> - <name name="bom_to_encoding" arity="1"/> + <name name="bom_to_encoding" arity="1" since=""/> <fsummary>Identify UTF byte order marks in a binary.</fsummary> <type name="endian"/> <type_desc variable="Bin"> @@ -156,7 +156,7 @@ </func> <func> - <name name="characters_to_binary" arity="1"/> + <name name="characters_to_binary" arity="1" since=""/> <fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary> <desc> <p>Same as <c>characters_to_binary(<anno>Data</anno>, unicode, @@ -165,7 +165,7 @@ </func> <func> - <name name="characters_to_binary" arity="2"/> + <name name="characters_to_binary" arity="2" since=""/> <fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary> <desc> <p>Same as <c>characters_to_binary(<anno>Data</anno>, @@ -174,7 +174,7 @@ </func> <func> - <name name="characters_to_binary" arity="3"/> + <name name="characters_to_binary" arity="3" since=""/> <fsummary>Convert a collection of characters to a UTF-8 binary.</fsummary> <desc> <p>Behaves as <seealso marker="#characters_to_list/2"> @@ -211,7 +211,7 @@ </func> <func> - <name name="characters_to_list" arity="1"/> + <name name="characters_to_list" arity="1" since=""/> <fsummary>Convert a collection of characters to a list of Unicode characters.</fsummary> <desc> @@ -220,7 +220,7 @@ </func> <func> - <name name="characters_to_list" arity="2"/> + <name name="characters_to_list" arity="2" since=""/> <fsummary>Convert a collection of characters to a list of Unicode characters.</fsummary> <desc> @@ -367,7 +367,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfc_list" arity="1"/> + <name name="characters_to_nfc_list" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a list of canonical equivalent composed Unicode characters.</fsummary> <desc> @@ -386,7 +386,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfc_binary" arity="1"/> + <name name="characters_to_nfc_binary" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a utf8 binary of canonical equivalent composed Unicode characters.</fsummary> <desc> @@ -404,7 +404,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfd_list" arity="1"/> + <name name="characters_to_nfd_list" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a list of canonical equivalent decomposed Unicode characters.</fsummary> <desc> @@ -423,7 +423,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfd_binary" arity="1"/> + <name name="characters_to_nfd_binary" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a utf8 binary of canonical equivalent decomposed Unicode characters.</fsummary> <desc> @@ -441,7 +441,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfkc_list" arity="1"/> + <name name="characters_to_nfkc_list" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a list of canonical equivalent composed Unicode characters.</fsummary> <desc> @@ -460,7 +460,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfkc_binary" arity="1"/> + <name name="characters_to_nfkc_binary" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a utf8 binary of compatibly equivalent composed Unicode characters.</fsummary> <desc> @@ -478,7 +478,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfkd_list" arity="1"/> + <name name="characters_to_nfkd_list" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a list of compatibly equivalent decomposed Unicode characters.</fsummary> <desc> @@ -497,7 +497,7 @@ decode_data(Data) -> </func> <func> - <name name="characters_to_nfkd_binary" arity="1"/> + <name name="characters_to_nfkd_binary" arity="1" since="OTP 20.0"/> <fsummary>Normalize characters to a utf8 binary of compatibly equivalent decomposed Unicode characters.</fsummary> <desc> @@ -515,7 +515,7 @@ decode_data(Data) -> </func> <func> - <name name="encoding_to_bom" arity="1"/> + <name name="encoding_to_bom" arity="1" since=""/> <fsummary>Create a binary UTF byte order mark from encoding.</fsummary> <type_desc variable="Bin"> A <c>binary()</c> such that <c>byte_size(<anno>Bin</anno>) >= 4</c>. diff --git a/lib/stdlib/doc/src/uri_string.xml b/lib/stdlib/doc/src/uri_string.xml index 88d4600611..ad443486c5 100644 --- a/lib/stdlib/doc/src/uri_string.xml +++ b/lib/stdlib/doc/src/uri_string.xml @@ -27,7 +27,7 @@ <date>2018-02-07</date> <rev>A</rev> </header> - <module>uri_string</module> + <module since="OTP 21.0">uri_string</module> <modulesummary>URI processing functions.</modulesummary> <description> <p>This module contains functions for parsing and handling URIs @@ -150,7 +150,7 @@ <funcs> <func> - <name name="compose_query" arity="1"/> + <name name="compose_query" arity="1" since="OTP 21.0"/> <fsummary>Compose urlencoded query string.</fsummary> <desc> <p>Composes a form-urlencoded <c><anno>QueryString</anno></c> based on a @@ -176,7 +176,7 @@ </func> <func> - <name name="compose_query" arity="2"/> + <name name="compose_query" arity="2" since="OTP 21.0"/> <fsummary>Compose urlencoded query string.</fsummary> <desc> <p>Same as <c>compose_query/1</c> but with an additional @@ -210,7 +210,7 @@ </func> <func> - <name name="dissect_query" arity="1"/> + <name name="dissect_query" arity="1" since="OTP 21.0"/> <fsummary>Dissect query string.</fsummary> <desc> <p>Dissects an urlencoded <c><anno>QueryString</anno></c> and returns a @@ -236,7 +236,7 @@ </func> <func> - <name name="normalize" arity="1"/> + <name name="normalize" arity="1" since="OTP 21.0"/> <fsummary>Syntax-based normalization.</fsummary> <desc> <p>Transforms an <c><anno>URI</anno></c> into a normalized form @@ -261,7 +261,7 @@ </func> <func> - <name name="normalize" arity="2"/> + <name name="normalize" arity="2" since="OTP 21.0"/> <fsummary>Syntax-based normalization.</fsummary> <desc> <p>Same as <c>normalize/1</c> but with an additional @@ -285,7 +285,7 @@ </func> <func> - <name name="parse" arity="1"/> + <name name="parse" arity="1" since="OTP 21.0"/> <fsummary>Parse URI into a map.</fsummary> <desc> <p>Parses an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> @@ -309,7 +309,7 @@ </func> <func> - <name name="recompose" arity="1"/> + <name name="recompose" arity="1" since="OTP 21.0"/> <fsummary>Recompose URI.</fsummary> <desc> <p>Creates an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> compliant @@ -332,7 +332,7 @@ </func> <func> - <name name="transcode" arity="2"/> + <name name="transcode" arity="2" since="OTP 21.0"/> <fsummary>Transcode URI.</fsummary> <desc> <p>Transcodes an <url href="https://www.ietf.org/rfc/rfc3986.txt">RFC 3986</url> diff --git a/lib/stdlib/doc/src/win32reg.xml b/lib/stdlib/doc/src/win32reg.xml index f4a4fa1626..5e2aed6062 100644 --- a/lib/stdlib/doc/src/win32reg.xml +++ b/lib/stdlib/doc/src/win32reg.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>win32reg.xml</file> </header> - <module>win32reg</module> + <module since="">win32reg</module> <modulesummary>Provides access to the registry on Windows.</modulesummary> <description> <p>This module provides read and write access to the @@ -112,7 +112,7 @@ hkdd HKEY_DYN_DATA</pre> <funcs> <func> - <name name="change_key" arity="2"/> + <name name="change_key" arity="2" since=""/> <fsummary>Move to a key in the registry.</fsummary> <desc> <p>Changes the current key to another key. Works like <c>cd</c>. @@ -122,7 +122,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="change_key_create" arity="2"/> + <name name="change_key_create" arity="2" since=""/> <fsummary>Move to a key, create it if it is not there.</fsummary> <desc> <p>Creates a key, or just changes to it, if it is already there. Works @@ -133,7 +133,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="close" arity="1"/> + <name name="close" arity="1" since=""/> <fsummary>Close the registry.</fsummary> <desc> <p>Closes the registry. After that, the <c><anno>RegHandle</anno></c> @@ -142,7 +142,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="current_key" arity="1"/> + <name name="current_key" arity="1" since=""/> <fsummary>Return the path to the current key.</fsummary> <desc> <p>Returns the path to the current key. This is the equivalent of @@ -153,7 +153,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="delete_key" arity="1"/> + <name name="delete_key" arity="1" since=""/> <fsummary>Delete the current key.</fsummary> <desc> <p>Deletes the current key, if it is valid. Calls the Win32 API @@ -166,7 +166,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="delete_value" arity="2"/> + <name name="delete_value" arity="2" since=""/> <fsummary>Delete the named value on the current key.</fsummary> <desc> <p>Deletes a named value on the current key. The atom <c>default</c> is @@ -176,7 +176,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="expand" arity="1"/> + <name name="expand" arity="1" since=""/> <fsummary>Expand a string with environment variables.</fsummary> <desc> <p>Expands a string containing environment variables between percent @@ -189,7 +189,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="format_error" arity="1"/> + <name name="format_error" arity="1" since=""/> <fsummary>Convert a POSIX error code to a string.</fsummary> <desc> <p>Converts a POSIX error code to a string @@ -198,7 +198,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="open" arity="1"/> + <name name="open" arity="1" since=""/> <fsummary>Open the registry for reading or writing.</fsummary> <desc> <p>Opens the registry for reading or writing. The current key is the @@ -211,7 +211,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="set_value" arity="3"/> + <name name="set_value" arity="3" since=""/> <fsummary>Set value at the current registry key with specified name. </fsummary> <desc> @@ -230,7 +230,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="sub_keys" arity="1"/> + <name name="sub_keys" arity="1" since=""/> <fsummary>Get subkeys to the current key.</fsummary> <desc> <p>Returns a list of subkeys to the current key. Calls the Win32 @@ -240,7 +240,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="value" arity="2"/> + <name name="value" arity="2" since=""/> <fsummary>Get the named value on the current key.</fsummary> <desc> <p>Retrieves the named value (or default) on the current key. @@ -251,7 +251,7 @@ hkdd HKEY_DYN_DATA</pre> </func> <func> - <name name="values" arity="1"/> + <name name="values" arity="1" since=""/> <fsummary>Get all values on the current key.</fsummary> <desc> <p>Retrieves a list of all values on the current key. The values diff --git a/lib/stdlib/doc/src/zip.xml b/lib/stdlib/doc/src/zip.xml index 0b5eac1e16..bb2ed7727a 100644 --- a/lib/stdlib/doc/src/zip.xml +++ b/lib/stdlib/doc/src/zip.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>zip.xml</file> </header> - <module>zip</module> + <module since="">zip</module> <modulesummary>Utility for reading and creating 'zip' archives. </modulesummary> <description> @@ -180,7 +180,7 @@ <funcs> <func> - <name name="foldl" arity="3"/> + <name name="foldl" arity="3" since="OTP R14B"/> <fsummary>Fold a function over all files in a zip archive.</fsummary> <desc> <p>Calls <c><anno>Fun</anno>(<anno>FileInArchive</anno>, <anno>GetInfo @@ -234,10 +234,10 @@ </func> <func> - <name name="list_dir" arity="1"/> - <name name="list_dir" arity="2"/> - <name name="table" arity="1" /> - <name name="table" arity="2"/> + <name name="list_dir" arity="1" since=""/> + <name name="list_dir" arity="2" since=""/> + <name name="table" arity="1" since=""/> + <name name="table" arity="2" since=""/> <fsummary>Retrieve the name of all files in a zip archive.</fsummary> <desc> <p><c>list_dir/1</c> retrieves all filenames in the zip archive @@ -263,7 +263,7 @@ </func> <func> - <name name="t" arity="1"/> + <name name="t" arity="1" since=""/> <fsummary>Print the name of each file in a zip archive.</fsummary> <desc> <p>Prints all filenames in the zip archive <c><anno>Archive</anno></c> @@ -272,7 +272,7 @@ </func> <func> - <name name="tt" arity="1"/> + <name name="tt" arity="1" since=""/> <fsummary>Print name and information for each file in a zip archive. </fsummary> <desc> @@ -283,10 +283,10 @@ </func> <func> - <name name="unzip" arity="1"/> - <name name="unzip" arity="2"/> - <name name="extract" arity="1"/> - <name name="extract" arity="2"/> + <name name="unzip" arity="1" since=""/> + <name name="unzip" arity="2" since=""/> + <name name="extract" arity="1" since=""/> + <name name="extract" arity="2" since=""/> <fsummary>Extract files from a zip archive.</fsummary> <desc> <p><c>unzip/1</c> extracts all files from a zip archive.</p> @@ -353,10 +353,10 @@ </func> <func> - <name name="zip" arity="2"/> - <name name="zip" arity="3"/> - <name name="create" arity="2"/> - <name name="create" arity="3"/> + <name name="zip" arity="2" since=""/> + <name name="zip" arity="3" since=""/> + <name name="create" arity="2" since=""/> + <name name="create" arity="3" since=""/> <fsummary>Create a zip archive with options.</fsummary> <desc> <p>Creates a zip archive containing the files specified in @@ -481,7 +481,7 @@ </func> <func> - <name name="zip_close" arity="1"/> + <name name="zip_close" arity="1" since=""/> <fsummary>Close an open archive.</fsummary> <desc> <p>Closes a zip archive, previously opened with @@ -492,8 +492,8 @@ </func> <func> - <name name="zip_get" arity="1"/> - <name name="zip_get" arity="2"/> + <name name="zip_get" arity="1" since=""/> + <name name="zip_get" arity="2" since=""/> <fsummary>Extract files from an open archive.</fsummary> <desc> <p>Extracts one or all files from an open archive.</p> @@ -505,7 +505,7 @@ </func> <func> - <name name="zip_list_dir" arity="1"/> + <name name="zip_list_dir" arity="1" since=""/> <fsummary>Return a table of files in open zip archive.</fsummary> <desc> <p>Returns the file list of an open zip archive. The first returned @@ -514,8 +514,8 @@ </func> <func> - <name name="zip_open" arity="1"/> - <name name="zip_open" arity="2"/> + <name name="zip_open" arity="1" since=""/> + <name name="zip_open" arity="2" since=""/> <fsummary>Open an archive and return a handle to it.</fsummary> <desc> <p>Opens a zip archive, and reads and saves its directory. This diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl index 9a600c1972..bb5d450cd6 100644 --- a/lib/stdlib/src/calendar.erl +++ b/lib/stdlib/src/calendar.erl @@ -693,14 +693,11 @@ local_offset(SystemTime, Unit) -> UniversalSecs = datetime_to_gregorian_seconds(UniversalTime), LocalSecs - UniversalSecs. +fraction_str(1, _Time) -> + ""; fraction_str(Factor, Time) -> - case Time rem Factor of - 0 -> - ""; - Fraction -> - FS = io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]), - string:trim(FS, trailing, "0") - end. + Fraction = Time rem Factor, + io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]). fraction(second, _) -> 0; diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index e9ac2fcdff..3ec78a2667 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -831,13 +831,15 @@ bif_clashes(Forms, #lint{nowarn_bif_clash=Nowarn} = St) -> %% not_deprecated(Forms, State0) -> State -not_deprecated(Forms, St0) -> +not_deprecated(Forms, #lint{compile=Opts}=St0) -> %% There are no line numbers in St0#lint.compile. MFAsL = [{MFA,L} || {attribute, L, compile, Args} <- Forms, {nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]), MFA <- lists:flatten([MFAs0])], - Nowarn = [MFA || {MFA,_L} <- MFAsL], + Nowarn = [MFA || + {nowarn_deprecated_function, MFAs0} <- Opts, + MFA <- lists:flatten([MFAs0])], ML = [{M,L} || {{M,_F,_A},L} <- MFAsL, is_atom(M)], St1 = foldl(fun ({M,L}, St2) -> check_module_name(M, L, St2) @@ -2262,8 +2264,7 @@ expr({'fun',Line,Body}, Vt, St) -> {[],St}; {function,M,F,A} -> %% New in R15. - {Bvt, St1} = expr_list([M,F,A], Vt, St), - {vtupdate(Bvt, Vt),St1} + expr_list([M,F,A], Vt, St) end; expr({named_fun,_,'_',Cs}, Vt, St) -> fun_clauses(Cs, Vt, St); diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index 9602f0bcd9..4ad94f2507 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -841,7 +841,7 @@ Erlang code. -type af_record_field(T) :: {'record_field', anno(), af_field_name(), T}. -type af_map_pattern() :: - {'map', anno(), [af_assoc_exact(abstract_expr)]}. + {'map', anno(), [af_assoc_exact(abstract_expr())]}. -type abstract_type() :: af_annotated_type() | af_atom() @@ -872,7 +872,7 @@ Erlang code. -type af_fun_type() :: {'type', anno(), 'fun', []} | {'type', anno(), 'fun', [{'type', anno(), 'any'} | abstract_type()]} - | {'type', anno(), 'fun', af_function_type()}. + | af_function_type(). -type af_integer_range_type() :: {'type', anno(), 'range', [af_singleton_integer_type()]}. @@ -924,10 +924,11 @@ Erlang code. -type af_function_constraint() :: [af_constraint()]. -type af_constraint() :: {'type', anno(), 'constraint', - af_lit_atom('is_subtype'), - [af_type_variable() | abstract_type()]}. % [V, T] + [af_lit_atom('is_subtype') | + [af_type_variable() | abstract_type()]]}. % [IsSubtype, [V, T]] -type af_singleton_integer_type() :: af_integer() + | af_character() | af_unary_op(af_singleton_integer_type()) | af_binary_op(af_singleton_integer_type()). diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl index caaaf8fa2e..1e18710738 100644 --- a/lib/stdlib/src/gen_fsm.erl +++ b/lib/stdlib/src/gen_fsm.erl @@ -411,12 +411,13 @@ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTime sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug, [Name, StateName, StateData, Mod, Time, HibernateAfterTimeout], Hib); {'EXIT', Parent, Reason} -> - terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug); + terminate( + Reason, Name, undefined, Msg, Mod, StateName, StateData, Debug); _Msg when Debug =:= [] -> handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout); _Msg -> Debug1 = sys:handle_debug(Debug, fun print_event/3, - {Name, StateName}, {in, Msg}), + Name, {in, Msg, StateName}), handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time, HibernateAfterTimeout, Debug1) end. @@ -431,7 +432,7 @@ system_continue(Parent, Debug, [Name, StateName, StateData, Mod, Time, Hibernate system_terminate(Reason, _Parent, Debug, [Name, StateName, StateData, Mod, _Time, _HibernateAfterTimeout]) -> - terminate(Reason, Name, [], Mod, StateName, StateData, Debug). + terminate(Reason, Name, undefined, [], Mod, StateName, StateData, Debug). system_code_change([Name, StateName, StateData, Mod, Time, HibernateAfterTimeout], _Module, OldVsn, Extra) -> @@ -452,7 +453,7 @@ system_replace_state(StateFun, [Name, StateName, StateData, Mod, Time, Hibernate %% Format debug messages. Print them as the call-back module sees %% them, not as the real erlang messages. Use trace for that. %%----------------------------------------------------------------- -print_event(Dev, {in, Msg}, {Name, StateName}) -> +print_event(Dev, {in, Msg, StateName}, Name) -> case Msg of {'$gen_event', Event} -> io:format(Dev, "*DBG* ~tp got event ~tp in state ~tw~n", @@ -461,6 +462,16 @@ print_event(Dev, {in, Msg}, {Name, StateName}) -> io:format(Dev, "*DBG* ~tp got all_state_event ~tp in state ~tw~n", [Name, Event, StateName]); + {'$gen_sync_event', {From,_Tag}, Event} -> + io:format(Dev, + "*DBG* ~tp got sync_event ~tp " + "from ~tw in state ~tw~n", + [Name, Event, From, StateName]); + {'$gen_sync_all_state_event', {From,_Tag}, Event} -> + io:format(Dev, + "*DBG* ~tp got sync_all_state_event ~tp " + "from ~tw in state ~tw~n", + [Name, Event, From, StateName]); {timeout, Ref, {'$gen_timer', Message}} -> io:format(Dev, "*DBG* ~tp got timer ~tp in state ~tw~n", @@ -473,11 +484,11 @@ print_event(Dev, {in, Msg}, {Name, StateName}) -> io:format(Dev, "*DBG* ~tp got ~tp in state ~tw~n", [Name, Msg, StateName]) end; -print_event(Dev, {out, Msg, To, StateName}, Name) -> +print_event(Dev, {out, Msg, {To,_Tag}, StateName}, Name) -> io:format(Dev, "*DBG* ~tp sent ~tp to ~tw~n" " and switched to state ~tw~n", [Name, Msg, To, StateName]); -print_event(Dev, return, {Name, StateName}) -> +print_event(Dev, {noreply, StateName}, Name) -> io:format(Dev, "*DBG* ~tp switched to state ~tw~n", [Name, StateName]). @@ -495,9 +506,9 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi reply(From, Reply), loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, []); {stop, Reason, NStateData} -> - terminate(Reason, Name, Msg, Mod, StateName, NStateData, []); + terminate(Reason, Name, From, Msg, Mod, StateName, NStateData, []); {stop, Reason, Reply, NStateData} when From =/= undefined -> - {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, + {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod, StateName, NStateData, [])), reply(From, Reply), exit(R); @@ -510,10 +521,10 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi error_logger=>#{tag=>warning_msg}}), loop(Parent, Name, StateName, StateData, Mod, infinity, HibernateAfterTimeout, []); {'EXIT', What} -> - terminate(What, Name, Msg, Mod, StateName, StateData, []); + terminate(What, Name, From, Msg, Mod, StateName, StateData, []); Reply -> terminate({bad_return_value, Reply}, - Name, Msg, Mod, StateName, StateData, []) + Name, From, Msg, Mod, StateName, StateData, []) end. handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTimeout, Debug) -> @@ -521,11 +532,11 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi case catch dispatch(Msg, Mod, StateName, StateData) of {next_state, NStateName, NStateData} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, - {Name, NStateName}, return), + Name, {noreply, NStateName}), loop(Parent, Name, NStateName, NStateData, Mod, infinity, HibernateAfterTimeout, Debug1); {next_state, NStateName, NStateData, Time1} -> Debug1 = sys:handle_debug(Debug, fun print_event/3, - {Name, NStateName}, return), + Name, {noreply, NStateName}), loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1); {reply, Reply, NStateName, NStateData} when From =/= undefined -> Debug1 = reply(Name, From, Reply, Debug, NStateName), @@ -534,17 +545,18 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, HibernateAfterTi Debug1 = reply(Name, From, Reply, Debug, NStateName), loop(Parent, Name, NStateName, NStateData, Mod, Time1, HibernateAfterTimeout, Debug1); {stop, Reason, NStateData} -> - terminate(Reason, Name, Msg, Mod, StateName, NStateData, Debug); + terminate( + Reason, Name, From, Msg, Mod, StateName, NStateData, Debug); {stop, Reason, Reply, NStateData} when From =/= undefined -> - {'EXIT', R} = (catch terminate(Reason, Name, Msg, Mod, + {'EXIT', R} = (catch terminate(Reason, Name, From, Msg, Mod, StateName, NStateData, Debug)), _ = reply(Name, From, Reply, Debug, StateName), exit(R); {'EXIT', What} -> - terminate(What, Name, Msg, Mod, StateName, StateData, Debug); + terminate(What, Name, From, Msg, Mod, StateName, StateData, Debug); Reply -> terminate({bad_return_value, Reply}, - Name, Msg, Mod, StateName, StateData, Debug) + Name, From, Msg, Mod, StateName, StateData, Debug) end. dispatch({'$gen_event', Event}, Mod, StateName, StateData) -> @@ -571,24 +583,25 @@ from(_) -> undefined. reply({To, Tag}, Reply) -> catch To ! {Tag, Reply}. -reply(Name, {To, Tag}, Reply, Debug, StateName) -> - reply({To, Tag}, Reply), +reply(Name, From, Reply, Debug, StateName) -> + reply(From, Reply), sys:handle_debug(Debug, fun print_event/3, Name, - {out, Reply, To, StateName}). + {out, Reply, From, StateName}). %%% --------------------------------------------------- %%% Terminate the server. %%% --------------------------------------------------- --spec terminate(term(), _, _, atom(), _, _, _) -> no_return(). +-spec terminate(term(), _, _, _, atom(), _, _, _) -> no_return(). -terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) -> +terminate(Reason, Name, From, Msg, Mod, StateName, StateData, Debug) -> case erlang:function_exported(Mod, terminate, 3) of true -> case catch Mod:terminate(Reason, StateName, StateData) of {'EXIT', R} -> FmtStateData = format_status(terminate, Mod, get(), StateData), - error_info(R, Name, Msg, StateName, FmtStateData, Debug), + error_info( + R, Name, From, Msg, StateName, FmtStateData, Debug), exit(R); _ -> ok @@ -605,29 +618,51 @@ terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) -> exit(Shutdown); _ -> FmtStateData1 = format_status(terminate, Mod, get(), StateData), - error_info(Reason,Name,Msg,StateName,FmtStateData1,Debug), + error_info( + Reason, Name, From, Msg, StateName, FmtStateData1, Debug), exit(Reason) end. -error_info(Reason, Name, Msg, StateName, StateData, Debug) -> +error_info(Reason, Name, From, Msg, StateName, StateData, Debug) -> + Log = sys:get_log(Debug), ?LOG_ERROR(#{label=>{gen_fsm,terminate}, name=>Name, last_message=>Msg, state_name=>StateName, state_data=>StateData, - reason=>Reason}, + log=>Log, + reason=>Reason, + client_info=>client_stacktrace(From)}, #{domain=>[otp], report_cb=>fun gen_fsm:format_log/1, error_logger=>#{tag=>error}}), - sys:print_log(Debug), ok. +client_stacktrace(undefined) -> + undefined; +client_stacktrace({Pid,_Tag}) -> + client_stacktrace(Pid); +client_stacktrace(Pid) when is_pid(Pid), node(Pid) =:= node() -> + case process_info(Pid, [current_stacktrace, registered_name]) of + undefined -> + {Pid,dead}; + [{current_stacktrace, Stacktrace}, {registered_name, []}] -> + {Pid,{Pid,Stacktrace}}; + [{current_stacktrace, Stacktrace}, {registered_name, Name}] -> + {Pid,{Name,Stacktrace}} + end; +client_stacktrace(Pid) when is_pid(Pid) -> + {Pid,remote}. + + format_log(#{label:={gen_fsm,terminate}, name:=Name, last_message:=Msg, state_name:=StateName, state_data:=StateData, - reason:=Reason}) -> + log:=Log, + reason:=Reason, + client_info:=ClientInfo}) -> Reason1 = case Reason of {undef,[{M,F,A,L}|MFAs]} -> @@ -645,27 +680,39 @@ format_log(#{label:={gen_fsm,terminate}, _ -> Reason end, + {ClientFmt,ClientArgs} = format_client_log(ClientInfo), {"** State machine ~tp terminating \n" ++ get_msg_str(Msg) ++ "** When State == ~tp~n" "** Data == ~tp~n" - "** Reason for termination = ~n** ~tp~n", - [Name, get_msg(Msg), StateName, StateData, Reason1]}; + "** Reason for termination ==~n** ~tp~n" ++ + case Log of + [] -> []; + _ -> "** Log ==~n** ~tp~n" + end ++ ClientFmt, + [Name|error_logger:limit_term(get_msg(Msg))] ++ + [StateName, + error_logger:limit_term(StateData), + error_logger:limit_term(Reason1) | + case Log of + [] -> []; + _ -> [[error_logger:limit_term(D) || D <- Log]] + end] ++ ClientArgs}; format_log(#{label:={gen_fsm,no_handle_info}, module:=Mod, message:=Msg}) -> {"** Undefined handle_info in ~p~n" "** Unhandled message: ~tp~n", - [Mod, Msg]}. + [Mod, error_logger:limit_term(Msg)]}. get_msg_str({'$gen_event', _Event}) -> "** Last event in was ~tp~n"; -get_msg_str({'$gen_sync_event', _Event}) -> - "** Last sync event in was ~tp~n"; +get_msg_str({'$gen_sync_event', _From, _Event}) -> + "** Last sync event in was ~tp from ~tw~n"; get_msg_str({'$gen_all_state_event', _Event}) -> "** Last event in was ~tp (for all states)~n"; -get_msg_str({'$gen_sync_all_state_event', _Event}) -> - "** Last sync event in was ~tp (for all states)~n"; +get_msg_str({'$gen_sync_all_state_event', _From, _Event}) -> + "** Last sync event in was ~tp (for all states) from ~tw~n"; get_msg_str({timeout, _Ref, {'$gen_timer', _Msg}}) -> "** Last timer event in was ~tp~n"; get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) -> @@ -673,13 +720,24 @@ get_msg_str({timeout, _Ref, {'$gen_event', _Msg}}) -> get_msg_str(_Msg) -> "** Last message in was ~tp~n". -get_msg({'$gen_event', Event}) -> Event; -get_msg({'$gen_sync_event', Event}) -> Event; -get_msg({'$gen_all_state_event', Event}) -> Event; -get_msg({'$gen_sync_all_state_event', Event}) -> Event; -get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> {timeout, Ref, Msg}; -get_msg({timeout, _Ref, {'$gen_event', Event}}) -> Event; -get_msg(Msg) -> Msg. +get_msg({'$gen_event', Event}) -> [Event]; +get_msg({'$gen_sync_event', {From,_Tag}, Event}) -> [Event,From]; +get_msg({'$gen_all_state_event', Event}) -> [Event]; +get_msg({'$gen_sync_all_state_event', {From,_Tag}, Event}) -> [Event,From]; +get_msg({timeout, Ref, {'$gen_timer', Msg}}) -> [{timeout, Ref, Msg}]; +get_msg({timeout, _Ref, {'$gen_event', Event}}) -> [Event]; +get_msg(Msg) -> [Msg]. + +format_client_log(undefined) -> + {"", []}; +format_client_log({From,dead}) -> + {"** Client ~p is dead~n", [From]}; +format_client_log({From,remote}) -> + {"** Client ~p is remote on node ~p~n", [From, node(From)]}; +format_client_log({_From,{Name,Stacktrace}}) -> + {"** Client ~tp stacktrace~n" + "** ~tp~n", + [Name, error_logger:limit_term(Stacktrace)]}. %%----------------------------------------------------------------- %% Status information @@ -689,18 +747,18 @@ format_status(Opt, StatusData) -> StatusData, Header = gen:format_status_header("Status for state machine", Name), - Log = sys:get_debug(log, Debug, []), - Specfic = format_status(Opt, Mod, PDict, StateData), - Specfic = case format_status(Opt, Mod, PDict, StateData) of - S when is_list(S) -> S; - S -> [S] - end, + Log = sys:get_log(Debug), + Specific = + case format_status(Opt, Mod, PDict, StateData) of + S when is_list(S) -> S; + S -> [S] + end, [{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}, {"Logged events", Log}, {"StateName", StateName}]} | - Specfic]. + Specific]. format_status(Opt, Mod, PDict, State) -> DefStatus = case Opt of diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 44e9231ebe..c7b6406f54 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -773,10 +773,10 @@ handle_common_reply(Reply, Parent, Name, From, Msg, Mod, HibernateAfterTimeout, terminate({bad_return_value, BadReply}, ?STACKTRACE(), Name, From, Msg, Mod, State, Debug) end. -reply(Name, {To, Tag}, Reply, State, Debug) -> - reply({To, Tag}, Reply), +reply(Name, From, Reply, State, Debug) -> + reply(From, Reply), sys:handle_debug(Debug, fun print_event/3, Name, - {out, Reply, To, State} ). + {out, Reply, From, State} ). %%----------------------------------------------------------------- @@ -810,7 +810,7 @@ system_replace_state(StateFun, [Name, State, Mod, Time, HibernateAfterTimeout]) print_event(Dev, {in, Msg}, Name) -> case Msg of {'$gen_call', {From, _Tag}, Call} -> - io:format(Dev, "*DBG* ~tp got call ~tp from ~w~n", + io:format(Dev, "*DBG* ~tp got call ~tp from ~tw~n", [Name, Call, From]); {'$gen_cast', Cast} -> io:format(Dev, "*DBG* ~tp got cast ~tp~n", @@ -818,8 +818,8 @@ print_event(Dev, {in, Msg}, Name) -> _ -> io:format(Dev, "*DBG* ~tp got ~tp~n", [Name, Msg]) end; -print_event(Dev, {out, Msg, To, State}, Name) -> - io:format(Dev, "*DBG* ~tp sent ~tp to ~w, new state ~tp~n", +print_event(Dev, {out, Msg, {To,_Tag}, State}, Name) -> + io:format(Dev, "*DBG* ~tp sent ~tp to ~tw, new state ~tp~n", [Name, Msg, To, State]); print_event(Dev, {noreply, State}, Name) -> io:format(Dev, "*DBG* ~tp new state ~tp~n", [Name, State]); @@ -885,16 +885,17 @@ error_info(_Reason, application_controller, _From, _Msg, _Mod, _State, _Debug) - %% of it instead ok; error_info(Reason, Name, From, Msg, Mod, State, Debug) -> + Log = sys:get_log(Debug), ?LOG_ERROR(#{label=>{gen_server,terminate}, name=>Name, last_message=>Msg, state=>format_status(terminate, Mod, get(), State), + log=>format_log_state(Mod, Log), reason=>Reason, client_info=>client_stacktrace(From)}, #{domain=>[otp], report_cb=>fun gen_server:format_log/1, error_logger=>#{tag=>error}}), - sys:print_log(Debug), ok. client_stacktrace(undefined) -> @@ -917,6 +918,7 @@ format_log(#{label:={gen_server,terminate}, name:=Name, last_message:=Msg, state:=State, + log:=Log, reason:=Reason, client_info:=Client}) -> Reason1 = @@ -934,20 +936,30 @@ format_log(#{label:={gen_server,terminate}, end end; _ -> - error_logger:limit_term(Reason) + Reason end, {ClientFmt,ClientArgs} = format_client_log(Client), + [LimitedMsg,LimitedState,LimitedReason|LimitedLog] = + [error_logger:limit_term(D) || D <- [Msg,State,Reason1|Log]], {"** Generic server ~tp terminating \n" "** Last message in was ~tp~n" "** When Server state == ~tp~n" - "** Reason for termination == ~n** ~tp~n" ++ ClientFmt, - [Name, Msg, error_logger:limit_term(State), Reason1] ++ ClientArgs}; + "** Reason for termination ==~n** ~tp~n" ++ + case LimitedLog of + [] -> []; + _ -> "** Log ==~n** ~tp~n" + end ++ ClientFmt, + [Name, LimitedMsg, LimitedState, LimitedReason] ++ + case LimitedLog of + [] -> []; + _ -> [LimitedLog] + end ++ ClientArgs}; format_log(#{label:={gen_server,no_handle_info}, module:=Mod, message:=Msg}) -> {"** Undefined handle_info in ~p~n" "** Unhandled message: ~tp~n", - [Mod, Msg]}. + [Mod, error_logger:limit_term(Msg)]}. format_client_log(undefined) -> {"", []}; @@ -958,7 +970,7 @@ format_client_log({From,remote}) -> format_client_log({_From,{Name,Stacktrace}}) -> {"** Client ~tp stacktrace~n" "** ~tp~n", - [Name, Stacktrace]}. + [Name, error_logger:limit_term(Stacktrace)]}. %%----------------------------------------------------------------- %% Status information @@ -966,16 +978,25 @@ format_client_log({_From,{Name,Stacktrace}}) -> format_status(Opt, StatusData) -> [PDict, SysState, Parent, Debug, [Name, State, Mod, _Time, _HibernateAfterTimeout]] = StatusData, Header = gen:format_status_header("Status for generic server", Name), - Log = sys:get_debug(log, Debug, []), - Specfic = case format_status(Opt, Mod, PDict, State) of + Log = sys:get_log(Debug), + Specific = case format_status(Opt, Mod, PDict, State) of S when is_list(S) -> S; S -> [S] end, [{header, Header}, {data, [{"Status", SysState}, {"Parent", Parent}, - {"Logged events", Log}]} | - Specfic]. + {"Logged events", format_log_state(Mod, Log)}]} | + Specific]. + +format_log_state(Mod, Log) -> + [case Event of + {out,Msg,From,State} -> + {out,Msg,From,format_status(terminate, Mod, get(), State)}; + {noreply,State} -> + {noreply,format_status(terminate, Mod, get(), State)}; + _ -> Event + end || Event <- Log]. format_status(Opt, Mod, PDict, State) -> DefStatus = case Opt of diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 24b268cd38..513118a874 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2016-2018. All Rights Reserved. +%% Copyright Ericsson AB 2016-2019. 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. @@ -374,50 +374,51 @@ timeout_event_type(Type) -> -define( + relative_timeout(T), + ((is_integer(T) andalso 0 =< (T)) orelse (T) =:= infinity)). + +-define( + absolute_timeout(T), + (is_integer(T) orelse (T) =:= infinity)). + +-define( STACKTRACE(), element(2, erlang:process_info(self(), current_stacktrace))). -define(not_sys_debug, []). %% %% This is a macro to only evaluate arguments if Debug =/= []. -%% Debug is evaluated multiple times. +%% Debug is evaluated 2 times. -define( - sys_debug(Debug, NameState, Entry), + sys_debug(Debug, Extra, SystemEvent), case begin Debug end of ?not_sys_debug -> begin Debug end; _ -> - sys_debug(begin Debug end, begin NameState end, begin Entry end) + sys_debug( + begin Debug end, begin Extra end, begin SystemEvent end) end). --record(state, +-record(params, {callback_mode = undefined :: callback_mode() | undefined, state_enter = false :: boolean(), + parent :: pid(), module :: atom(), - name :: atom(), - state :: term(), - data :: term(), + name :: atom() | pid(), + hibernate_after = infinity :: timeout() + }). + +-record(state, + {state_data = {undefined,undefined} :: + {State :: term(),Data :: term()}, postponed = [] :: [{event_type(),term()}], - %% - timer_refs = #{} :: % timer ref => the timer's event type - #{reference() => timeout_event_type()}, - timer_types = #{} :: % timer's event type => timer ref - #{timeout_event_type() => reference()}, - cancel_timers = 0 :: non_neg_integer(), - %% We add a timer to both timer_refs and timer_types - %% when we start it. When we request an asynchronous - %% timer cancel we remove it from timer_types. When - %% the timer cancel message arrives we remove it from - %% timer_refs. - %% - hibernate = false :: boolean(), - hibernate_after = infinity :: timeout()}). - --record(trans_opts, - {hibernate = false, - postpone = false, - timeouts_r = [], - next_events_r = []}). + timers = {#{},#{}} :: + {%% TimerRef => TimeoutType + TimerRefs :: #{reference() => timeout_event_type()}, + %% TimeoutType => TimerRef + TimeoutTypes :: #{timeout_event_type() => reference()}}, + hibernate = false :: boolean() + }). %%%========================================================================== %%% API @@ -586,7 +587,12 @@ enter_loop(Module, Opts, State, Data, Server_or_Actions) -> enter_loop(Module, Opts, State, Data, Server, Actions) -> is_atom(Module) orelse error({atom,Module}), Parent = gen:get_parent(), - enter(Module, Opts, State, Data, Server, Actions, Parent). + Name = gen:get_proc_name(Server), + Debug = gen:debug_options(Name, Opts), + HibernateAfterTimeout = gen:hibernate_after(Opts), + enter( + Parent, Debug, Module, Name, HibernateAfterTimeout, + State, Data, Actions). %%--------------------------------------------------------------------------- %% API helpers @@ -658,36 +664,29 @@ send(Proc, Msg) -> ok. %% Here the init_it/6 and enter_loop/5,6,7 functions converge -enter(Module, Opts, State, Data, Server, Actions, Parent) -> +enter( + Parent, Debug, Module, Name, HibernateAfterTimeout, + State, Data, Actions) -> %% The values should already have been type checked - Name = gen:get_proc_name(Server), - Debug = gen:debug_options(Name, Opts), - HibernateAfterTimeout = gen:hibernate_after(Opts), - Events = [], - Event = {internal,init_state}, + Q = [{internal,init_state}], %% We enforce {postpone,false} to ensure that %% our fake Event gets discarded, thought it might get logged - NewActions = listify(Actions) ++ [{postpone,false}], - S = - #state{ + Actions_1 = listify(Actions) ++ [{postpone,false}], + P = + #params{ + parent = Parent, module = Module, name = Name, - state = State, - data = Data, hibernate_after = HibernateAfterTimeout}, - CallEnter = true, - NewDebug = ?sys_debug(Debug, {Name,State}, {enter,Event,State}), - case call_callback_mode(S) of - #state{} = NewS -> - loop_event_actions_list( - Parent, NewDebug, NewS, - Events, Event, State, Data, false, - NewActions, CallEnter); - [Class,Reason,Stacktrace] -> - terminate( - Class, Reason, Stacktrace, NewDebug, - S, [Event|Events]) - end. + S = #state{state_data = {State,Data}}, + Debug_1 = ?sys_debug(Debug, Name, {enter,State}), + loop_callback_mode( + P, Debug_1, S, Q, {State,Data}, + %% Tunneling Actions through CallbackEvent here... + %% Special path to go to action handling, after first + %% finding out the callback mode. CallbackEvent is + %% a 2-tuple and Actions a list, which achieves this distinction. + Actions_1). %%%========================================================================== %%% gen callbacks @@ -695,34 +694,46 @@ enter(Module, Opts, State, Data, Server, Actions, Parent) -> init_it(Starter, self, ServerRef, Module, Args, Opts) -> init_it(Starter, self(), ServerRef, Module, Args, Opts); init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> + Name = gen:get_proc_name(ServerRef), + Debug = gen:debug_options(Name, Opts), + HibernateAfterTimeout = gen:hibernate_after(Opts), try Module:init(Args) of Result -> - init_result(Starter, Parent, ServerRef, Module, Result, Opts) + init_result( + Starter, Parent, ServerRef, Module, Result, + Name, Debug, HibernateAfterTimeout) catch Result -> - init_result(Starter, Parent, ServerRef, Module, Result, Opts); + init_result( + Starter, Parent, ServerRef, Module, Result, + Name, Debug, HibernateAfterTimeout); Class:Reason:Stacktrace -> - Name = gen:get_proc_name(ServerRef), gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), error_info( - Class, Reason, Stacktrace, - #state{name = Name}, - []), + Class, Reason, Stacktrace, Debug, + #params{parent = Parent, name = Name, module = Module}, + #state{}, []), erlang:raise(Class, Reason, Stacktrace) end. %%--------------------------------------------------------------------------- %% gen callbacks helpers -init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> +init_result( + Starter, Parent, ServerRef, Module, Result, + Name, Debug, HibernateAfterTimeout) -> case Result of {ok,State,Data} -> proc_lib:init_ack(Starter, {ok,self()}), - enter(Module, Opts, State, Data, ServerRef, [], Parent); + enter( + Parent, Debug, Module, Name, HibernateAfterTimeout, + State, Data, []); {ok,State,Data,Actions} -> proc_lib:init_ack(Starter, {ok,self()}), - enter(Module, Opts, State, Data, ServerRef, Actions, Parent); + enter( + Parent, Debug, Module, Name, HibernateAfterTimeout, + State, Data, Actions); {stop,Reason} -> gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), @@ -732,31 +743,34 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> proc_lib:init_ack(Starter, ignore), exit(normal); _ -> - 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(), - #state{name = Name}, - []), + error, Error, ?STACKTRACE(), Debug, + #params{parent = Parent, name = Name, module = Module}, + #state{}, []), exit(Error) end. %%%========================================================================== %%% sys callbacks +%%% +%%% We use {P,S} as state (Misc) for the sys module, +%%% wrap/unwrap it for the server loop* and update +%%% P#params{parent = Parent}. -system_continue(Parent, Debug, S) -> - loop(Parent, Debug, S). +system_continue(Parent, Debug, {P,S}) -> + loop(update_parent(P, Parent), Debug, S). -system_terminate(Reason, _Parent, Debug, S) -> - terminate(exit, Reason, ?STACKTRACE(), Debug, S, []). +system_terminate(Reason, Parent, Debug, {P,S}) -> + terminate( + exit, Reason, ?STACKTRACE(), + update_parent(P, Parent), Debug, S, []). system_code_change( - #state{ - module = Module, - state = State, - data = Data} = S, + {#params{module = Module} = P, + #state{state_data = {State,Data}} = S}, _Mod, OldVsn, Extra) -> case try Module:code_change(OldVsn, State, Data, Extra) @@ -766,44 +780,54 @@ system_code_change( of {ok,NewState,NewData} -> {ok, - S#state{ - callback_mode = undefined, - state = NewState, - data = NewData}}; + {P#params{callback_mode = undefined}, + S#state{state_data = {NewState,NewData}}}}; {ok,_} = Error -> error({case_clause,Error}); Error -> Error end. -system_get_state(#state{state = State, data = Data}) -> - {ok,{State,Data}}. +system_get_state({_P,#state{state_data = State_Data}}) -> + {ok,State_Data}. system_replace_state( - StateFun, - #state{ - state = State, - data = Data} = S) -> - {NewState,NewData} = Result = StateFun({State,Data}), - {ok,Result,S#state{state = NewState, data = NewData}}. + StateFun, {P,#state{state_data = State_Data} = S}) -> + %% + NewState_NewData = StateFun(State_Data), + {ok,NewState_NewData,{P,S#state{state_data = NewState_NewData}}}. format_status( Opt, [PDict,SysState,Parent,Debug, - #state{name = Name, postponed = P} = S]) -> + {#params{name = Name} = P, + #state{postponed = Postponed} = S}]) -> Header = gen:format_status_header("Status for state machine", Name), - Log = sys:get_debug(log, Debug, []), + Log = sys:get_log(Debug), [{header,Header}, {data, [{"Status",SysState}, {"Parent",Parent}, {"Logged Events",Log}, - {"Postponed",P}]} | - case format_status(Opt, PDict, S) of + {"Postponed",Postponed}]} | + case format_status(Opt, PDict, update_parent(P, Parent), S) of L when is_list(L) -> L; T -> [T] end]. +%% Update #params.parent only if it differs. This should not +%% be possible today (OTP-22.0), but could happen for example +%% if someone implements changing a server's parent +%% in a new sys call. +-compile({inline, update_parent/2}). +update_parent(P, Parent) -> + case P of + #params{parent = Parent} -> + P; + #params{} -> + P#params{parent = Parent} + end. + %%--------------------------------------------------------------------------- %% Format debug messages. Print them as the call-back module sees %% them, not as the real erlang messages. Use trace for that. @@ -812,34 +836,46 @@ format_status( sys_debug(Debug, NameState, Entry) -> sys:handle_debug(Debug, fun print_event/3, NameState, Entry). -print_event(Dev, {in,Event}, {Name,State}) -> - io:format( - Dev, "*DBG* ~tp receive ~ts in state ~tp~n", - [Name,event_string(Event),State]); -print_event(Dev, {out,Reply,{To,_Tag}}, {Name,State}) -> - io:format( - Dev, "*DBG* ~tp send ~tp to ~p from state ~tp~n", - [Name,Reply,To,State]); -print_event(Dev, {terminate,Reason}, {Name,State}) -> - io:format( - Dev, "*DBG* ~tp terminate ~tp in state ~tp~n", - [Name,Reason,State]); -print_event(Dev, {Tag,Event,NextState}, {Name,State}) -> - StateString = - case NextState of - State -> - io_lib:format("~tp", [State]); - _ -> - io_lib:format("~tp => ~tp", [State,NextState]) - end, - io:format( - Dev, "*DBG* ~tp ~tw ~ts in state ~ts~n", - [Name,Tag,event_string(Event),StateString]). +print_event(Dev, SystemEvent, Name) -> + case SystemEvent of + {in,Event,State} -> + io:format( + Dev, "*DBG* ~tp receive ~ts in state ~tp~n", + [Name,event_string(Event),State]); + {code_change,Event,State} -> + io:format( + Dev, "*DBG* ~tp receive ~ts after code change in state ~tp~n", + [Name,event_string(Event),State]); + {out,Reply,{To,_Tag}} -> + io:format( + Dev, "*DBG* ~tp send ~tp to ~tw~n", + [Name,Reply,To]); + {enter,State} -> + io:format( + Dev, "*DBG* ~tp enter in state ~tp~n", + [Name,State]); + {terminate,Reason,State} -> + io:format( + Dev, "*DBG* ~tp terminate ~tp in state ~tp~n", + [Name,Reason,State]); + {Tag,Event,State,NextState} + when Tag =:= postpone; Tag =:= consume -> + StateString = + case NextState of + State -> + io_lib:format("~tp", [State]); + _ -> + io_lib:format("~tp => ~tp", [State,NextState]) + end, + io:format( + Dev, "*DBG* ~tp ~tw ~ts in state ~ts~n", + [Name,Tag,event_string(Event),StateString]) + end. event_string(Event) -> case Event of {{call,{Pid,_Tag}},Request} -> - io_lib:format("call ~tp from ~w", [Request,Pid]); + io_lib:format("call ~tp from ~tw", [Request,Pid]); {EventType,EventContent} -> io_lib:format("~tw ~tp", [EventType,EventContent]) end. @@ -847,964 +883,1169 @@ event_string(Event) -> %%%========================================================================== %%% Internal callbacks -wakeup_from_hibernate(Parent, Debug, S) -> +wakeup_from_hibernate(P, Debug, S) -> %% It is a new message that woke us up so we have to receive it now - loop_receive(Parent, Debug, S). + loop_receive(P, Debug, S). %%%========================================================================== -%%% State Machine engine implementation of proc_lib/gen server +%%% State Machine engine implementation on proc_lib/gen %% Server loop, consists of all loop* functions %% and detours through sys:handle_system_message/7 and proc_lib:hibernate/3 +%% +%% The loop tries to keep all temporary values in arguments +%% and takes shortcuts for ?not_sys_debug, empty lists, etc. +%% The engine state #state{} is picked apart during the loop, +%% new values are kept in arguments, and a new #state{} is +%% composed at the end of the loop. #params{} collect engine +%% state fields that rarely changes. +%% +%% The loop is optimized a bit for staying in the loop, assuming that +%% system events are rare. So a detour to sys requires re-packing +%% of the engine state. %% Entry point for system_continue/3 -loop(Parent, Debug, #state{hibernate = true, cancel_timers = 0} = S) -> - loop_hibernate(Parent, Debug, S); -loop(Parent, Debug, S) -> - loop_receive(Parent, Debug, S). +%% +loop(P, Debug, #state{hibernate = true} = S) -> + loop_hibernate(P, Debug, S); +loop(P, Debug, S) -> + loop_receive(P, Debug, S). -loop_hibernate(Parent, Debug, S) -> +%% Go to hibernation +%% +loop_hibernate(P, Debug, S) -> %% %% Does not return but restarts process at %% wakeup_from_hibernate/3 that jumps to loop_receive/3 %% - proc_lib:hibernate( - ?MODULE, wakeup_from_hibernate, [Parent,Debug,S]), + proc_lib:hibernate(?MODULE, wakeup_from_hibernate, [P, Debug, S]), error( {should_not_have_arrived_here_but_instead_in, - {wakeup_from_hibernate,3}}). + {?MODULE,wakeup_from_hibernate,3}}). + %% Entry point for wakeup_from_hibernate/3 +%% +%% Receive a new process message +%% loop_receive( - Parent, Debug, #state{hibernate_after = HibernateAfterTimeout} = S) -> + #params{hibernate_after = HibernateAfterTimeout} = P, Debug, S) -> %% receive Msg -> case Msg of + {'$gen_call',From,Request} -> + loop_receive_result(P, Debug, S, {{call,From},Request}); + {'$gen_cast',Cast} -> + loop_receive_result(P, Debug, S, {cast,Cast}); + %% + {timeout,TimerRef,TimeoutMsg} -> + {TimerRefs,TimeoutTypes} = S#state.timers, + case TimerRefs of + #{TimerRef := TimeoutType} -> + %% Our timer + Timers = + {maps:remove(TimerRef, TimerRefs), + maps:remove(TimeoutType, TimeoutTypes)}, + S_1 = S#state{timers = Timers}, + loop_receive_result( + P, Debug, S_1, {TimeoutType,TimeoutMsg}); + #{} -> + loop_receive_result(P, Debug, S, {info,Msg}) + end; + %% {system,Pid,Req} -> %% Does not return but tail recursively calls %% system_continue/3 that jumps to loop/3 sys:handle_system_msg( - Req, Pid, Parent, ?MODULE, Debug, S, + Req, Pid, P#params.parent, ?MODULE, Debug, + {P,S}, S#state.hibernate); - {'EXIT',Parent,Reason} = EXIT -> - %% EXIT is not a 2-tuple therefore - %% not an event but this will stand out - %% in the crash report... - Q = [EXIT], - terminate(exit, Reason, ?STACKTRACE(), Debug, S, Q); - {timeout,TimerRef,TimerMsg} -> - #state{ - timer_refs = TimerRefs, - timer_types = TimerTypes} = S, - case TimerRefs of - #{TimerRef := TimerType} -> - %% We know of this timer; is it a running - %% timer or a timer being cancelled that - %% managed to send a late timeout message? - case TimerTypes of - #{TimerType := TimerRef} -> - %% The timer type maps back to this - %% timer ref, so it was a running timer - %% Unregister the triggered timeout - NewTimerRefs = - maps:remove(TimerRef, TimerRefs), - NewTimerTypes = - maps:remove(TimerType, TimerTypes), - loop_receive_result( - Parent, Debug, - S#state{ - timer_refs = NewTimerRefs, - timer_types = NewTimerTypes}, - TimerType, TimerMsg); - _ -> - %% This was a late timeout message - %% from timer being cancelled, so - %% ignore it and expect a cancel_timer - %% msg shortly - loop_receive(Parent, Debug, S) - end; - _ -> - %% Not our timer; present it as an event - loop_receive_result(Parent, Debug, S, info, Msg) - end; - {cancel_timer,TimerRef,_} -> - #state{ - timer_refs = TimerRefs, - cancel_timers = CancelTimers, - hibernate = Hibernate} = S, - case TimerRefs of - #{TimerRef := _} -> - %% We must have requested a cancel - %% of this timer so it is already - %% removed from TimerTypes - NewTimerRefs = - maps:remove(TimerRef, TimerRefs), - NewCancelTimers = CancelTimers - 1, - NewS = - S#state{ - timer_refs = NewTimerRefs, - cancel_timers = NewCancelTimers}, - if - Hibernate =:= true, NewCancelTimers =:= 0 -> - %% No more cancel_timer msgs to expect; - %% we can hibernate - loop_hibernate(Parent, Debug, NewS); - NewCancelTimers >= 0 -> % Assert - loop_receive(Parent, Debug, NewS) - end; - _ -> - %% Not our cancel_timer msg; - %% present it as an event - loop_receive_result(Parent, Debug, S, info, Msg) - end; - _ -> - %% External msg - case Msg of - {'$gen_call',From,Request} -> - loop_receive_result( - Parent, Debug, S, {call,From}, Request); - {'$gen_cast',Cast} -> - loop_receive_result(Parent, Debug, S, cast, Cast); + {'EXIT',Pid,Reason} -> + case P#params.parent of + Pid -> + terminate( + exit, Reason, ?STACKTRACE(), P, Debug, S, []); _ -> - loop_receive_result(Parent, Debug, S, info, Msg) - end + loop_receive_result(P, Debug, S, {info,Msg}) + end; + %% + _ -> + loop_receive_result(P, Debug, S, {info,Msg}) end after - HibernateAfterTimeout -> - loop_hibernate(Parent, Debug, S) + HibernateAfterTimeout -> + loop_hibernate(P, Debug, S) end. -loop_receive_result(Parent, ?not_sys_debug, S, Type, Content) -> +%% We have received an event +%% +loop_receive_result(P, ?not_sys_debug = Debug, S, Event) -> %% Here is the queue of not yet handled events created Events = [], - loop_event(Parent, ?not_sys_debug, S, Events, Type, Content); + loop_event(P, Debug, S, Event, Events); loop_receive_result( - Parent, Debug, #state{name = Name, state = State} = S, Type, Content) -> - NewDebug = sys_debug(Debug, {Name,State}, {in,{Type,Content}}), + #params{name = Name, callback_mode = CallbackMode} = P, Debug, + #state{state_data = {State,_Data}} = S, Event) -> + Debug_1 = + case CallbackMode of + undefined -> + sys_debug(Debug, Name, {code_change,Event,State}); + _ -> + sys_debug(Debug, Name, {in,Event,State}) + end, %% Here is the queue of not yet handled events created Events = [], - loop_event(Parent, NewDebug, S, Events, Type, Content). + loop_event(P, Debug_1, S, Event, Events). -%% Entry point for handling an event, received or enqueued +%% Handle one event; received or enqueued +%% loop_event( - Parent, Debug, #state{hibernate = Hibernate} = S, - Events, Type, Content) -> + P, Debug, #state{hibernate = true} = S, Event, Events) -> %% - case Hibernate of - true -> - %% - %% If (this old) Hibernate is true here it can only be - %% because it was set from an event action - %% and we did not go into hibernation since there were - %% events in queue, so we do what the user - %% might rely on i.e collect garbage which - %% would have happened if we actually hibernated - %% and immediately was awakened. - %% - _ = garbage_collect(), - loop_event_state_function( - Parent, Debug, S, Events, Type, Content); - false -> - loop_event_state_function( - Parent, Debug, S, Events, Type, Content) - end. + %% If (this old) Hibernate is true here it can only be + %% because it was set from an event action + %% and we did not go into hibernation since there were + %% events in queue, so we do what the user + %% might rely on i.e collect garbage which + %% would have happened if we actually hibernated + %% and immediately was awakened. + %% + _ = garbage_collect(), + loop_event_handler(P, Debug, S, Event, Events); +loop_event(P, Debug, S, Event, Events) -> + loop_event_handler(P, Debug, S, Event, Events). -%% Call the state function -loop_event_state_function( - Parent, Debug, - #state{state = State, data = Data} = S, - Events, Type, Content) -> +%% Call the state function, eventually +%% +-compile({inline, [loop_event_handler/5]}). +loop_event_handler( + P, Debug, #state{state_data = State_Data} = S, Event, Events) -> %% %% The field 'hibernate' in S is now invalid and will be - %% restored when looping back to loop/3 or loop_event/6. + %% restored when looping back to loop/3 or loop_event/5. %% - Event = {Type,Content}, - TransOpts = false, - case call_state_function(S, Type, Content, State, Data) of - {Result, NewS} -> - loop_event_result( - Parent, Debug, NewS, - Events, Event, State, Data, TransOpts, Result); - [Class,Reason,Stacktrace] -> - terminate( - Class, Reason, Stacktrace, Debug, S, [Event|Events]) - end. + Q = [Event|Events], + loop_callback_mode(P, Debug, S, Q, State_Data, Event). -%% Make a state enter call to the state function -loop_event_state_enter( - Parent, Debug, #state{state = PrevState} = S, - Events, Event, NextState, NewData, TransOpts) -> +%% Figure out the callback mode +%% +loop_callback_mode( + #params{callback_mode = undefined} = P, Debug, S, + Q, State_Data, CallbackEvent) -> %% - case call_state_function(S, enter, PrevState, NextState, NewData) of - {Result, NewS} -> - loop_event_result( - Parent, Debug, NewS, - Events, Event, NextState, NewData, TransOpts, Result); - [Class,Reason,Stacktrace] -> + Module = P#params.module, + try Module:callback_mode() of + CallbackMode -> + loop_callback_mode_result( + P, Debug, S, + Q, State_Data, CallbackEvent, + CallbackMode, listify(CallbackMode), undefined, false) + catch + CallbackMode -> + loop_callback_mode_result( + P, Debug, S, + Q, State_Data, CallbackEvent, + CallbackMode, listify(CallbackMode), undefined, false); + Class:Reason:Stacktrace -> terminate( - Class, Reason, Stacktrace, Debug, S, [Event|Events]) + Class, Reason, Stacktrace, P, Debug, S, Q) + end; +loop_callback_mode(P, Debug, S, Q, State_Data, CallbackEvent) -> + loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent). + +%% Check the result of Module:callback_mode() +%% +loop_callback_mode_result( + P, Debug, S, Q, State_Data, CallbackEvent, + CallbackMode, [H|T], NewCallbackMode, NewStateEnter) -> + %% + case callback_mode(H) of + true -> + loop_callback_mode_result( + P, Debug, S, Q, State_Data, CallbackEvent, + CallbackMode, T, H, NewStateEnter); + false -> + case state_enter(H) of + true -> + loop_callback_mode_result( + P, Debug, S, Q, State_Data, CallbackEvent, + CallbackMode, T, NewCallbackMode, true); + false -> + terminate( + error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE(), + P, Debug, S, Q) + end + end; +loop_callback_mode_result( + P, Debug, S, Q, State_Data, CallbackEvent, + CallbackMode, [], NewCallbackMode, NewStateEnter) -> + %% + case NewCallbackMode of + undefined -> + terminate( + error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE(), + P, Debug, S, Q); + _ -> + P_1 = + P#params{ + callback_mode = NewCallbackMode, + state_enter = NewStateEnter}, + loop_state_callback( + P_1, Debug, S, Q, State_Data, CallbackEvent) end. -%% Process the result from the state function. -%% When TransOpts =:= false it was a state function call, -%% otherwise it is an option tuple and it was a state enter call. + +%% Make a state enter call to the state function, we loop back here +%% from further down if state enter calls are enabled +%% +loop_state_enter( + P, Debug, #state{state_data = {PrevState,_PrevData}} = S, + Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) -> + %% + StateCall = false, + CallbackEvent = {enter,PrevState}, + loop_state_callback( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, CallbackEvent). + +%% Make a state call (not state enter call) to the state function +%% +loop_state_callback(P, Debug, S, Q, State_Data, CallbackEvent) -> + NextEventsR = [], + Hibernate = false, + TimeoutsR = [], + Postpone = false, + StateCall = true, + loop_state_callback( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, CallbackEvent). +%% +loop_state_callback( + #params{callback_mode = CallbackMode, module = Module} = P, + Debug, S, Q, {State,Data} = State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, {Type,Content}) -> + try + case CallbackMode of + state_functions -> + Module:State(Type, Content, Data); + handle_event_function -> + Module:handle_event(Type, Content, State, Data) + end + of + Result -> + loop_state_callback_result( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Result) + catch + Result -> + loop_state_callback_result( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Result); + Class:Reason:Stacktrace -> + terminate(Class, Reason, Stacktrace, P, Debug, S, Q) + end; +loop_state_callback( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Actions) when is_list(Actions) -> + %% Tunneled actions from enter/8 + CallEnter = true, + loop_actions_list( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions). + +%% Process the result from the state function %% -loop_event_result( - Parent, Debug, S, - Events, Event, State, Data, TransOpts, Result) -> +loop_state_callback_result( + P, Debug, S, Q, {State,_Data} = State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + StateCall, Result) -> %% case Result of {next_state,State,NewData} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, NewData, TransOpts, - [], false); + loop_actions( + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false); {next_state,NextState,NewData} - when TransOpts =:= false -> - loop_event_actions( - Parent, Debug, S, - Events, Event, NextState, NewData, TransOpts, - [], true); + when StateCall -> + loop_actions( + P, Debug, S, Q, {NextState,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true); {next_state,_NextState,_NewData} -> terminate( error, {bad_state_enter_return_from_state_function,Result}, - ?STACKTRACE(), Debug, + ?STACKTRACE(), P, Debug, S#state{ - state = State, data = Data, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]); + state_data = State_Data, + hibernate = Hibernate}, + Q); {next_state,State,NewData,Actions} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, NewData, TransOpts, - Actions, false); + loop_actions( + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false, StateCall, Actions); {next_state,NextState,NewData,Actions} - when TransOpts =:= false -> - loop_event_actions( - Parent, Debug, S, - Events, Event, NextState, NewData, TransOpts, - Actions, true); + when StateCall -> + loop_actions( + P, Debug, S, Q, {NextState,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true, StateCall, Actions); {next_state,_NextState,_NewData,_Actions} -> terminate( error, {bad_state_enter_return_from_state_function,Result}, - ?STACKTRACE(), Debug, + ?STACKTRACE(), P, Debug, S#state{ - state = State, data = Data, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]); + state_data = State_Data, + hibernate = Hibernate}, + Q); %% {keep_state,NewData} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, NewData, TransOpts, - [], false); + loop_actions( + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false); {keep_state,NewData,Actions} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, NewData, TransOpts, - Actions, false); + loop_actions( + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false, StateCall, Actions); %% keep_state_and_data -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, Data, TransOpts, - [], false); + loop_actions( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false); {keep_state_and_data,Actions} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, Data, TransOpts, - Actions, false); + loop_actions( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + false, StateCall, Actions); %% {repeat_state,NewData} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, NewData, TransOpts, - [], true); + loop_actions( + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true); {repeat_state,NewData,Actions} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, NewData, TransOpts, - Actions, true); + loop_actions( + P, Debug, S, Q, {State,NewData}, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true, StateCall, Actions); %% repeat_state_and_data -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, Data, TransOpts, - [], true); + loop_actions( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true); {repeat_state_and_data,Actions} -> - loop_event_actions( - Parent, Debug, S, - Events, Event, State, Data, TransOpts, - Actions, true); + loop_actions( + P, Debug, S, Q, State_Data, + NextEventsR, Hibernate, TimeoutsR, Postpone, + true, StateCall, Actions); %% stop -> terminate( - exit, normal, ?STACKTRACE(), Debug, + exit, normal, ?STACKTRACE(), P, Debug, S#state{ - state = State, data = Data, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]); + state_data = State_Data, + hibernate = Hibernate}, + Q); {stop,Reason} -> terminate( - exit, Reason, ?STACKTRACE(), Debug, + exit, Reason, ?STACKTRACE(), P, Debug, S#state{ - state = State, data = Data, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]); + state_data = State_Data, + hibernate = Hibernate}, + Q); {stop,Reason,NewData} -> terminate( - exit, Reason, ?STACKTRACE(), Debug, + exit, Reason, ?STACKTRACE(), P, Debug, S#state{ - state = State, data = NewData, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]); + state_data = {State,NewData}, + hibernate = Hibernate}, + Q); %% {stop_and_reply,Reason,Replies} -> reply_then_terminate( - exit, Reason, ?STACKTRACE(), Debug, + exit, Reason, ?STACKTRACE(), P, Debug, S#state{ - state = State, data = Data, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events], Replies); + state_data = State_Data, + hibernate = Hibernate}, + Q, Replies); {stop_and_reply,Reason,Replies,NewData} -> reply_then_terminate( - exit, Reason, ?STACKTRACE(), Debug, + exit, Reason, ?STACKTRACE(), P, Debug, S#state{ - state = State, data = NewData, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events], Replies); + state_data = {State,NewData}, + hibernate = Hibernate}, + Q, Replies); %% _ -> terminate( error, {bad_return_from_state_function,Result}, - ?STACKTRACE(), Debug, + ?STACKTRACE(), P, Debug, S#state{ - state = State, data = Data, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]) + state_data = State_Data, + hibernate = Hibernate}, + Q) end. %% Ensure that Actions are a list -loop_event_actions( - Parent, Debug, S, - Events, Event, NextState, NewerData, TransOpts, - Actions, CallEnter) -> - loop_event_actions_list( - Parent, Debug, S, - Events, Event, NextState, NewerData, TransOpts, - listify(Actions), CallEnter). - -%% Process actions from the state function -loop_event_actions_list( - Parent, Debug, #state{state_enter = StateEnter} = S, - Events, Event, NextState, NewerData, TransOpts, - Actions, CallEnter) -> +%% +loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, _StateCall, []) -> + loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter); +loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions) -> %% - case parse_actions(TransOpts, Debug, S, Actions) of - {NewDebug,NewTransOpts} - when StateEnter, CallEnter -> - loop_event_state_enter( - Parent, NewDebug, S, - Events, Event, NextState, NewerData, NewTransOpts); - {NewDebug,NewTransOpts} -> - loop_event_done( - Parent, NewDebug, S, - Events, Event, NextState, NewerData, NewTransOpts); - [Class,Reason,Stacktrace,NewDebug] -> - terminate( - Class, Reason, Stacktrace, NewDebug, - S#state{ - state = NextState, - data = NewerData, - hibernate = hibernate_in_trans_opts(TransOpts)}, - [Event|Events]) + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, listify(Actions)). +%% +%% Shortcut for no actions +-compile({inline, [loop_actions/10]}). +loop_actions( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter) -> + %% + %% Shortcut for no actions + case CallEnter andalso P#params.state_enter of + true -> + loop_state_enter( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone); + false -> + loop_state_transition( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) end. --compile({inline, [hibernate_in_trans_opts/1]}). -hibernate_in_trans_opts(false) -> - (#trans_opts{})#trans_opts.hibernate; -hibernate_in_trans_opts(#trans_opts{hibernate = Hibernate}) -> - Hibernate. - -parse_actions(false, Debug, S, Actions) -> - parse_actions(true, Debug, S, Actions, #trans_opts{}); -parse_actions(TransOpts, Debug, S, Actions) -> - parse_actions(false, Debug, S, Actions, TransOpts). +%% Process the returned actions %% -parse_actions(_StateCall, Debug, _S, [], TransOpts) -> - {Debug,TransOpts}; -parse_actions(StateCall, Debug, S, [Action|Actions], TransOpts) -> +loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, _StateCall, []) -> + %% + case P#params.state_enter of + true when CallEnter -> + loop_state_enter( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone); + _ -> + loop_state_transition( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) + end; +loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, [Action|Actions]) -> + %% case Action of %% Actual actions {reply,From,Reply} -> - parse_actions_reply( - StateCall, Debug, S, Actions, TransOpts, From, Reply); + loop_actions_reply( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + From, Reply); %% %% Actions that set options - {hibernate,NewHibernate} when is_boolean(NewHibernate) -> - parse_actions( - StateCall, Debug, S, Actions, - TransOpts#trans_opts{hibernate = NewHibernate}); + {hibernate,Hibernate_1} when is_boolean(Hibernate_1) -> + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate_1, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); hibernate -> - parse_actions( - StateCall, Debug, S, Actions, - TransOpts#trans_opts{hibernate = true}); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, true, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); %% - {postpone,NewPostpone} when not NewPostpone orelse StateCall -> - parse_actions( - StateCall, Debug, S, Actions, - TransOpts#trans_opts{postpone = NewPostpone}); + {postpone,Postpone_1} when not Postpone_1 orelse StateCall -> + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone_1, + CallEnter, StateCall, Actions); postpone when StateCall -> - parse_actions( - StateCall, Debug, S, Actions, - TransOpts#trans_opts{postpone = true}); + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, true, + CallEnter, StateCall, Actions); postpone -> - [error, - {bad_state_enter_action_from_state_function,Action}, - ?STACKTRACE(), - Debug]; + terminate( + error, + {bad_state_enter_action_from_state_function,Action}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q); %% {next_event,Type,Content} -> - parse_actions_next_event( - StateCall, Debug, S, Actions, TransOpts, Type, Content); + loop_actions_next_event( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Type, Content); %% - _ -> - parse_actions_timeout( - StateCall, Debug, S, Actions, TransOpts, Action) + Timeout -> + loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Timeout) end. -parse_actions_reply( - StateCall, ?not_sys_debug = Debug, S, Actions, TransOpts, +%% Process a reply action +%% +loop_actions_reply( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, From, Reply) -> %% case from(From) of true -> + %% No need for a separate ?not_sys_debug clause here + %% since the external call to erlang:'!'/2 in reply/2 + %% will cause swap out of all live registers anyway reply(From, Reply), - parse_actions(StateCall, ?not_sys_debug, S, Actions, TransOpts); - false -> - [error, - {bad_action_from_state_function,{reply,From,Reply}}, - ?STACKTRACE(), Debug] - end; -parse_actions_reply( - StateCall, Debug, #state{name = Name, state = State} = S, - Actions, TransOpts, From, Reply) -> - %% - case from(From) of - true -> - reply(From, Reply), - NewDebug = sys_debug(Debug, {Name,State}, {out,Reply,From}), - parse_actions(StateCall, NewDebug, S, Actions, TransOpts); + Debug_1 = ?sys_debug(Debug, P#params.name, {out,Reply,From}), + loop_actions_list( + P, Debug_1, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); false -> - [error, - {bad_action_from_state_function,{reply,From,Reply}}, - ?STACKTRACE(), Debug] + terminate( + error, + {bad_action_from_state_function,{reply,From,Reply}}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end. -parse_actions_next_event( - StateCall, ?not_sys_debug = Debug, S, - Actions, TransOpts, Type, Content) -> - case event_type(Type) of - true when StateCall -> - NextEventsR = TransOpts#trans_opts.next_events_r, - parse_actions( - StateCall, ?not_sys_debug, S, Actions, - TransOpts#trans_opts{ - next_events_r = [{Type,Content}|NextEventsR]}); - _ -> - [error, - {bad_state_enter_action_from_state_function, - {next_event,Type,Content}}, - ?STACKTRACE(), Debug] - end; -parse_actions_next_event( - StateCall, Debug, #state{name = Name, state = State} = S, - Actions, TransOpts, Type, Content) -> +%% Process a next_event action +%% +loop_actions_next_event( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Type, Content) -> case event_type(Type) of true when StateCall -> - NewDebug = sys_debug(Debug, {Name,State}, {in,{Type,Content}}), - NextEventsR = TransOpts#trans_opts.next_events_r, - parse_actions( - StateCall, NewDebug, S, Actions, - TransOpts#trans_opts{ - next_events_r = [{Type,Content}|NextEventsR]}); + NextEvent = {Type,Content}, + case Debug of + ?not_sys_debug -> + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + [NextEvent|NextEventsR], + Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions); + _ -> + Name = P#params.name, + {State,_Data} = S#state.state_data, + Debug_1 = + sys_debug(Debug, Name, {in,{Type,Content},State}), + loop_actions_list( + P, Debug_1, S, Q, NextState_NewData, + [NextEvent|NextEventsR], + Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions) + end; _ -> - [error, - {bad_state_enter_action_from_state_function, - {next_event,Type,Content}}, - ?STACKTRACE(), - Debug] + terminate( + error, + {if + StateCall -> + bad_action_from_state_function; + true -> + bad_state_enter_action_from_state_function + end, + {next_event,Type,Content}}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end. -parse_actions_timeout( - StateCall, Debug, S, Actions, TransOpts, - {TimeoutType,Time,TimerMsg,TimerOpts} = AbsoluteTimeout) -> +%% Process a timeout action, or also any unrecognized action +%% +loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + {TimeoutType,Time,TimeoutMsg,TimeoutOpts} = Timeout) -> %% - case classify_timeout(TimeoutType, Time, listify(TimerOpts)) of - absolute -> - parse_actions_timeout_add( - StateCall, Debug, S, Actions, - TransOpts, AbsoluteTimeout); - relative -> - RelativeTimeout = {TimeoutType,Time,TimerMsg}, - parse_actions_timeout_add( - StateCall, Debug, S, Actions, - TransOpts, RelativeTimeout); - badarg -> - [error, - {bad_action_from_state_function,AbsoluteTimeout}, - ?STACKTRACE(), - Debug] + case timeout_event_type(TimeoutType) of + true -> + case listify(TimeoutOpts) of + %% Optimization cases + [] when ?relative_timeout(Time) -> + RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + [{abs,true}] when ?absolute_timeout(Time) -> + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [Timeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + [{abs,false}] when ?relative_timeout(Time) -> + RelativeTimeout = {TimeoutType,Time,TimeoutMsg}, + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + %% Generic case + TimeoutOptsList -> + case parse_timeout_opts_abs(TimeoutOptsList) of + true when ?absolute_timeout(Time) -> + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [Timeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + false when ?relative_timeout(Time) -> + RelativeTimeout = + {TimeoutType,Time,TimeoutMsg}, + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + badarg -> + terminate( + error, + {bad_action_from_state_function,Timeout}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) + end + end; + false -> + terminate( + error, + {bad_action_from_state_function,Timeout}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end; -parse_actions_timeout( - StateCall, Debug, S, Actions, TransOpts, - {TimeoutType,Time,_} = RelativeTimeout) -> - case classify_timeout(TimeoutType, Time, []) of - relative -> - parse_actions_timeout_add( - StateCall, Debug, S, Actions, - TransOpts, RelativeTimeout); - badarg -> - [error, - {bad_action_from_state_function,RelativeTimeout}, - ?STACKTRACE(), - Debug] +loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, + {TimeoutType,Time,_} = Timeout) -> + %% + case timeout_event_type(TimeoutType) of + true when ?relative_timeout(Time) -> + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [Timeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + _ -> + terminate( + error, + {bad_action_from_state_function,Timeout}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end; -parse_actions_timeout( - StateCall, Debug, S, Actions, TransOpts, - Time) -> - case classify_timeout(timeout, Time, []) of - relative -> +loop_actions_timeout( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone, + CallEnter, StateCall, Actions, Time) -> + %% + if + ?relative_timeout(Time) -> RelativeTimeout = {timeout,Time,Time}, - parse_actions_timeout_add( - StateCall, Debug, S, Actions, - TransOpts, RelativeTimeout); - badarg -> - [error, - {bad_action_from_state_function,Time}, - ?STACKTRACE(), - Debug] + loop_actions_list( + P, Debug, S, Q, NextState_NewData, + NextEventsR, Hibernate, + [RelativeTimeout|TimeoutsR], Postpone, + CallEnter, StateCall, Actions); + true -> + terminate( + error, + {bad_action_from_state_function,Time}, + ?STACKTRACE(), P, Debug, + S#state{ + state_data = NextState_NewData, + hibernate = Hibernate}, + Q) end. -parse_actions_timeout_add( - StateCall, Debug, S, Actions, - #trans_opts{timeouts_r = TimeoutsR} = TransOpts, Timeout) -> - parse_actions( - StateCall, Debug, S, Actions, - TransOpts#trans_opts{timeouts_r = [Timeout|TimeoutsR]}). - %% Do the state transition -loop_event_done( - Parent, ?not_sys_debug, - #state{postponed = P} = S, -%% #state{postponed = will_not_happen = P} = S, - Events, Event, NextState, NewData, - #trans_opts{ - postpone = Postpone, hibernate = Hibernate, - timeouts_r = [], next_events_r = NextEventsR}) -> - %% - %% Optimize the simple cases i.e no debug and no timer changes, - %% by duplicate stripped down code - %% - %% Fast path - %% - case Postpone of - true -> - loop_event_done_fast( - Parent, Hibernate, S, - Events, [Event|P], NextState, NewData, NextEventsR); - false -> - loop_event_done_fast( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR) - end; -loop_event_done( - Parent, Debug_0, - #state{ - state = State, postponed = P_0, - timer_refs = TimerRefs_0, timer_types = TimerTypes_0, - cancel_timers = CancelTimers_0} = S, - Events_0, Event_0, NextState, NewData, - #trans_opts{ - hibernate = Hibernate, timeouts_r = TimeoutsR, - postpone = Postpone, next_events_r = NextEventsR}) -> +%% +loop_state_transition( + P, Debug, #state{state_data = {State,_Data}, postponed = Postponed} = S, + [Event|Events], {NextState,_NewData} = NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postpone) -> %% %% All options have been collected and next_events are buffered. %% Do the actual state transition. %% - %% Full feature path - %% - [Debug_1|P_1] = % Move current event to postponed if Postpone + Postponed_1 = % Move current event to postponed if Postpone case Postpone of true -> - [?sys_debug( - Debug_0, - {S#state.name,State}, - {postpone,Event_0,NextState}), - Event_0|P_0]; + [Event|Postponed]; false -> - [?sys_debug( - Debug_0, - {S#state.name,State}, - {consume,Event_0,NextState})|P_0] + Postponed end, - {Events_2,P_2, - Timers_2} = - %% Cancel the event timeout - if - NextState =:= State -> - {Events_0,P_1, - cancel_timer_by_type( - timeout, {TimerTypes_0,CancelTimers_0})}; - true -> - %% Move all postponed events to queue - %% and cancel the state timeout - {lists:reverse(P_1, Events_0),[], - cancel_timer_by_type( - state_timeout, - cancel_timer_by_type( - timeout, {TimerTypes_0,CancelTimers_0}))} - end, - {TimerRefs_3,{TimerTypes_3,CancelTimers_3},TimeoutEvents} = - %% Stop and start timers - parse_timers(TimerRefs_0, Timers_2, TimeoutsR), - %% Place next events last in reversed queue - Events_3R = lists:reverse(Events_2, NextEventsR), - %% Enqueue immediate timeout events - Events_4R = prepend_timeout_events(TimeoutEvents, Events_3R), - loop_event_done( - Parent, Debug_1, - S#state{ - state = NextState, - data = NewData, - postponed = P_2, - timer_refs = TimerRefs_3, - timer_types = TimerTypes_3, - cancel_timers = CancelTimers_3, - hibernate = Hibernate}, - lists:reverse(Events_4R)). - -loop_event_done(Parent, Debug, S, Q) -> -%% io:format( -%% "loop_event_done:~n" -%% " state = ~p, data = ~p, postponed = ~p~n " -%% " timer_refs = ~p, timer_types = ~p, cancel_timers = ~p.~n" -%% " Q = ~p.~n", -%% [S#state.state,S#state.data,S#state.postponed, -%% S#state.timer_refs,S#state.timer_types,S#state.cancel_timers, -%% Q]), - case Q of - [] -> - %% Get a new event - loop(Parent, Debug, S); - [{Type,Content}|Events] -> - %% Loop until out of enqueued events - loop_event(Parent, Debug, S, Events, Type, Content) + case Debug of + ?not_sys_debug -> + %% Optimization for no sys_debug + %% - avoid calling sys_debug/3 + if + NextState =:= State -> + loop_keep_state( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1); + true -> + loop_state_change( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1) + end; + _ -> + %% With sys_debug + Name = P#params.name, + Debug_1 = + case Postpone of + true -> + sys_debug( + Debug, Name, + {postpone,Event,State,NextState}); + false -> + sys_debug( + Debug, Name, + {consume,Event,State,NextState}) + end, + if + NextState =:= State -> + loop_keep_state( + P, Debug_1, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1); + true -> + loop_state_change( + P, Debug_1, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed_1) + end end. +%% State transition to the same state +%% +loop_keep_state( + P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed) -> + %% + %% Cancel event timeout + %% + case TimeoutTypes of + %% Optimization + %% - only cancel timer when there is a timer to cancel + #{timeout := TimerRef} -> + %% Event timeout active + loop_next_events( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + cancel_timer_by_ref_and_type( + TimerRef, timeout, TimerRefs, TimeoutTypes)); + _ -> + %% No event timeout active + loop_next_events( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers) + end. -%% Fast path -%% -%% Cancel event timer and state timer only if running -loop_event_done_fast( - Parent, Hibernate, - #state{ - state = NextState, - timer_types = TimerTypes, - cancel_timers = CancelTimers} = S, - Events, P, NextState, NewData, NextEventsR) -> - %% Same state - case TimerTypes of - #{timeout := _} -> - %% Event timeout active - loop_event_done_fast_2( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, - cancel_timer_by_type( - timeout, {TimerTypes,CancelTimers})); - _ -> - %% No event timeout active - loop_event_done_fast_2( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, - {TimerTypes,CancelTimers}) - end; -loop_event_done_fast( - Parent, Hibernate, - #state{ - timer_types = TimerTypes, - cancel_timers = CancelTimers} = S, - Events, P, NextState, NewData, NextEventsR) -> - %% State change - case TimerTypes of - #{timeout := _} -> - %% Event timeout active - %% - cancel state_timeout too since it is faster than inspecting - loop_event_done_fast( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, - cancel_timer_by_type( - state_timeout, - cancel_timer_by_type( - timeout, {TimerTypes,CancelTimers}))); - #{state_timeout := _} -> - %% State_timeout active but not event timeout - loop_event_done_fast( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, - cancel_timer_by_type( - state_timeout, {TimerTypes,CancelTimers})); +%% State transition to a different state +%% +loop_state_change( + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed) -> + %% + %% Retry postponed events + %% + case Postponed of + [] -> + loop_state_change( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR); + [E1] -> + loop_state_change( + P, Debug, S, + [E1|Events], NextState_NewData, + NextEventsR, Hibernate, TimeoutsR); + [E2,E1] -> + loop_state_change( + P, Debug, S, + [E1,E2|Events], NextState_NewData, + NextEventsR, Hibernate, TimeoutsR); _ -> - %% No event nor state_timeout active - loop_event_done_fast( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, - {TimerTypes,CancelTimers}) + loop_state_change( + P, Debug, S, + lists:reverse(Postponed, Events), NextState_NewData, + NextEventsR, Hibernate, TimeoutsR) end. %% -%% Retry postponed events -loop_event_done_fast( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, TimerTypes_CancelTimers) -> - case P of - %% Handle 0..2 postponed events without list reversal since - %% that will move out all live registers and back again - [] -> - loop_event_done_fast_2( - Parent, Hibernate, S, - Events, [], NextState, NewData, NextEventsR, - TimerTypes_CancelTimers); - [E] -> - loop_event_done_fast_2( - Parent, Hibernate, S, - [E|Events], [], NextState, NewData, NextEventsR, - TimerTypes_CancelTimers); - [E1,E2] -> - loop_event_done_fast_2( - Parent, Hibernate, S, - [E2,E1|Events], [], NextState, NewData, NextEventsR, - TimerTypes_CancelTimers); +loop_state_change( + P, Debug, #state{timers = {TimerRefs,TimeoutTypes} = Timers} = S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR) -> + %% + %% Cancel state and event timeout + %% + case TimeoutTypes of + %% Optimization + %% - only cancel timeout when there is an active timeout + %% + #{state_timeout := TimerRef} -> + %% State timeout active + %% - cancel event timeout too since it is faster than inspecting + loop_next_events( + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, [], + cancel_timer_by_type( + timeout, + cancel_timer_by_ref_and_type( + TimerRef, state_timeout, TimerRefs, TimeoutTypes))); + #{timeout := TimerRef} -> + %% Event timeout active but not state timeout + %% - cancel event timeout only + loop_next_events( + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, [], + cancel_timer_by_ref_and_type( + TimerRef, timeout, TimerRefs, TimeoutTypes)); _ -> - %% A bit slower path - loop_event_done_fast_2( - Parent, Hibernate, S, - lists:reverse(P, Events), [], NextState, NewData, NextEventsR, - TimerTypes_CancelTimers) + %% No state nor event timeout active. + loop_next_events( + P, Debug, S, Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, [], + Timers) end. + +%% Continue state transition with processing of +%% inserted events and timeout events %% -%% Fast path +loop_next_events( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, [], Postponed, + Timers) -> + %% + %% Optimization when there are no timeout actions + %% hence no timeout zero events to append to Events + %% - avoid loop_timeouts + loop_done( + P, Debug, + S#state{ + state_data = NextState_NewData, + postponed = Postponed, + timers = Timers, + hibernate = Hibernate}, + NextEventsR, Events); +loop_next_events( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers) -> + %% + Seen = #{}, + TimeoutEvents = [], + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents). + +%% Continue state transition with processing of timeout events %% -loop_event_done_fast_2( - Parent, Hibernate, S, - Events, P, NextState, NewData, NextEventsR, - {TimerTypes,CancelTimers}) -> +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, [], Postponed, + Timers, _Seen, TimeoutEvents) -> %% - NewS = + S_1 = S#state{ - state = NextState, - data = NewData, - postponed = P, - timer_types = TimerTypes, - cancel_timers = CancelTimers, + state_data = NextState_NewData, + postponed = Postponed, + timers = Timers, hibernate = Hibernate}, - case NextEventsR of - %% Handle 0..2 next events without list reversal since - %% that will move out all live registers and back again + case TimeoutEvents of [] -> - loop_event_done(Parent, ?not_sys_debug, NewS, Events); - [E] -> - loop_event_done(Parent, ?not_sys_debug, NewS, [E|Events]); - [E2,E1] -> - loop_event_done(Parent, ?not_sys_debug, NewS, [E1,E2|Events]); + loop_done(P, Debug, S_1, NextEventsR, Events); _ -> - %% A bit slower path - loop_event_done( - Parent, ?not_sys_debug, NewS, lists:reverse(NextEventsR, Events)) + case Events of + [] -> + loop_prepend_timeout_events( + P, Debug, S_1, TimeoutEvents, + NextEventsR); + [E1] -> + loop_prepend_timeout_events( + P, Debug, S_1, TimeoutEvents, + [E1|NextEventsR]); + [E2,E1] -> + loop_prepend_timeout_events( + P, Debug, S_1, TimeoutEvents, + [E1,E2|NextEventsR]); + _ -> + loop_prepend_timeout_events( + P, Debug, S_1, TimeoutEvents, + lists:reverse(Events, NextEventsR)) + end + end; +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, [Timeout|TimeoutsR], Postponed, + Timers, Seen, TimeoutEvents) -> + %% + case Timeout of + {TimeoutType,Time,TimeoutMsg} -> + %% Relative timeout + case Seen of + #{TimeoutType := _} -> + %% Type seen before - ignore + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType, Time, TimeoutMsg, []) + end; + {TimeoutType,Time,TimeoutMsg,TimeoutOpts} -> + %% Absolute timeout + case Seen of + #{TimeoutType := _} -> + %% Type seen before - ignore + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType, Time, TimeoutMsg, listify(TimeoutOpts)) + end end. - -%%--------------------------------------------------------------------------- -%% Server loop helpers - -call_callback_mode(#state{module = Module} = S) -> - try Module:callback_mode() of - CallbackMode -> - callback_mode_result(S, CallbackMode) - catch - CallbackMode -> - callback_mode_result(S, CallbackMode); - Class:Reason:Stacktrace -> - [Class,Reason,Stacktrace] +%% +loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType, Time, TimeoutMsg, TimeoutOpts) -> + %% + case Time of + infinity -> + %% Cancel any running timer + loop_timeouts_cancel( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, TimeoutEvents, + TimeoutType); + 0 when TimeoutOpts =:= [] -> + %% Relative timeout zero + %% - cancel any running timer + %% handle timeout zero events later + %% + loop_timeouts_cancel( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen, [{TimeoutType,TimeoutMsg}|TimeoutEvents], + TimeoutType); + _ -> + %% (Re)start the timer + TimerRef = + erlang:start_timer(Time, self(), TimeoutMsg, TimeoutOpts), + {TimerRefs,TimeoutTypes} = Timers, + case TimeoutTypes of + #{TimeoutType := OldTimerRef} -> + %% Cancel the running timer, + %% update the timeout type, + %% insert the new timer ref, + %% and remove the old timer ref + Timers_1 = + {maps:remove( + OldTimerRef, + TimerRefs#{TimerRef => TimeoutType}), + TimeoutTypes#{TimeoutType := TimerRef}}, + cancel_timer(OldTimerRef), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, TimeoutEvents); + #{} -> + %% Insert the new timer type and ref + Timers_1 = + {TimerRefs#{TimerRef => TimeoutType}, + TimeoutTypes#{TimeoutType => TimerRef}}, + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, TimeoutEvents) + end end. -callback_mode_result(S, CallbackMode) -> - callback_mode_result( - S, CallbackMode, listify(CallbackMode), undefined, false). -%% -callback_mode_result(_S, CallbackMode, [], undefined, _StateEnter) -> - [error, - {bad_return_from_callback_mode,CallbackMode}, - ?STACKTRACE()]; -callback_mode_result(S, _CallbackMode, [], CBMode, StateEnter) -> - S#state{callback_mode = CBMode, state_enter = StateEnter}; -callback_mode_result(S, CallbackMode, [H|T], CBMode, StateEnter) -> - case callback_mode(H) of - true -> - callback_mode_result(S, CallbackMode, T, H, StateEnter); - false -> - case state_enter(H) of - true -> - callback_mode_result(S, CallbackMode, T, CBMode, true); - false -> - [error, - {bad_return_from_callback_mode,CallbackMode}, - ?STACKTRACE()] - end +%% Loop helper to cancel a timeout +%% +loop_timeouts_cancel( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + {TimerRefs,TimeoutTypes} = Timers, Seen, TimeoutEvents, + TimeoutType) -> + %% This function body should have been: + %% Timers_1 = cancel_timer_by_type(TimeoutType, Timers), + %% loop_timeouts( + %% P, Debug, S, + %% Events, NextState_NewData, + %% NextEventsR, Hibernate, TimeoutsR, Postponed, + %% Timers_1, Seen#{TimeoutType => true}, TimeoutEvents). + %% + %% Explicitly separate cases to get separate code paths for when + %% the map key exists vs. not, since otherwise the external call + %% to erlang:cancel_timer/1 and to map:remove/2 within + %% cancel_timer_by_type/2 would cause all live registers + %% to be saved to and restored from the stack also for + %% the case when the map key TimeoutType does not exist + case TimeoutTypes of + #{TimeoutType := TimerRef} -> + Timers_1 = + cancel_timer_by_ref_and_type( + TimerRef, TimeoutType, TimerRefs, TimeoutTypes), + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers_1, Seen#{TimeoutType => true}, TimeoutEvents); + #{} -> + loop_timeouts( + P, Debug, S, + Events, NextState_NewData, + NextEventsR, Hibernate, TimeoutsR, Postponed, + Timers, Seen#{TimeoutType => true}, TimeoutEvents) end. +%% Continue state transition with prepending timeout zero events +%% before event queue reversal i.e appending timeout zero events +%% +loop_prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR) -> + {Debug_1,Events_1R} = + prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR), + loop_done(P, Debug_1, S, Events_1R, []). -call_state_function( - #state{callback_mode = undefined} = S, Type, Content, State, Data) -> - case call_callback_mode(S) of - #state{} = NewS -> - call_state_function(NewS, Type, Content, State, Data); - Error -> - Error - end; -call_state_function( - #state{callback_mode = CallbackMode, module = Module} = S, - Type, Content, State, Data) -> - try - case CallbackMode of - state_functions -> - Module:State(Type, Content, Data); - handle_event_function -> - Module:handle_event(Type, Content, State, Data) - end - of - Result -> - {Result,S} - catch - Result -> - {Result,S}; - Class:Reason:Stacktrace -> - [Class,Reason,Stacktrace] +%% Place inserted events first in the event queue +%% +loop_done(P, Debug, S, NextEventsR, Events) -> + case NextEventsR of + [] -> + loop_done(P, Debug, S, Events); + [E1] -> + loop_done(P, Debug, S, [E1|Events]); + [E2,E1] -> + loop_done(P, Debug, S, [E1,E2|Events]); + _ -> + loop_done(P, Debug, S, lists:reverse(NextEventsR, Events)) end. - - -%% -> absolute | relative | badarg -classify_timeout(TimeoutType, Time, Opts) -> - case timeout_event_type(TimeoutType) of - true -> - classify_time(false, Time, Opts); - false -> - badarg +%% +%% State transition is done, keep looping if there are +%% enqueued events, otherwise get a new event +%% +loop_done(P, Debug, S, Q) -> +%%% io:format( +%%% "loop_done: state_data = ~p,~n" +%%% " postponed = ~p, Q = ~p,~n", +%%% " timers = ~p.~n" +%%% [S#state.state_data,,S#state.postponed,Q,S#state.timers]), + case Q of + [] -> + %% Get a new event + loop(P, Debug, S); + [Event|Events] -> + %% Loop until out of enqueued events + loop_event(P, Debug, S, Event, Events) end. -classify_time(Abs, Time, []) -> - case Abs of - true when - is_integer(Time); - Time =:= infinity -> - absolute; - false when - is_integer(Time), 0 =< Time; - Time =:= infinity -> - relative; - _ -> - badarg - end; -classify_time(_, Time, [{abs,Abs}|Opts]) when is_boolean(Abs) -> - classify_time(Abs, Time, Opts); -classify_time(_, _, Opts) when is_list(Opts) -> - badarg. +%%--------------------------------------------------------------------------- +%% Server loop helpers -%% Stop and start timers as well as create timeout zero events -%% and pending event timer +%% Parse an option list for erlang:start_timer/4 to figure out +%% if the timeout will be absolute or relative %% -%% Stop and start timers non-event timers -parse_timers(TimerRefs, Timers, TimeoutsR) -> - parse_timers(TimerRefs, Timers, TimeoutsR, #{}, []). +parse_timeout_opts_abs(Opts) -> + parse_timeout_opts_abs(Opts, false). %% -parse_timers( - TimerRefs, Timers, [], _Seen, TimeoutEvents) -> - %% - {TimerRefs,Timers,TimeoutEvents}; -parse_timers( - TimerRefs, Timers, [Timeout|TimeoutsR], Seen, TimeoutEvents) -> - %% - case Timeout of - {TimerType,Time,TimerMsg,TimerOpts} -> - %% Absolute timer - parse_timers( - TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents, - TimerType, Time, TimerMsg, listify(TimerOpts)); - %% Relative timers below - {TimerType,0,TimerMsg} -> - parse_timers( - TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents, - TimerType, zero, TimerMsg, []); - {TimerType,Time,TimerMsg} -> - parse_timers( - TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents, - TimerType, Time, TimerMsg, []) - end. - -parse_timers( - TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents, - TimerType, Time, TimerMsg, TimerOpts) -> - case Seen of - #{TimerType := _} -> - %% Type seen before - ignore - parse_timers( - TimerRefs, Timers, TimeoutsR, Seen, TimeoutEvents); - #{} -> - %% Unseen type - handle - NewSeen = Seen#{TimerType => true}, - case Time of - infinity -> - %% Cancel any running timer - parse_timers( - TimerRefs, cancel_timer_by_type(TimerType, Timers), - TimeoutsR, NewSeen, TimeoutEvents); - zero -> - %% Cancel any running timer - %% Handle zero time timeouts later - parse_timers( - TimerRefs, cancel_timer_by_type(TimerType, Timers), - TimeoutsR, NewSeen, - [{TimerType,TimerMsg}|TimeoutEvents]); - _ -> - %% (Re)start the timer - TimerRef = - erlang:start_timer( - Time, self(), TimerMsg, TimerOpts), - case Timers of - {#{TimerType := OldTimerRef} = TimerTypes, - CancelTimers} -> - %% Cancel the running timer - cancel_timer(OldTimerRef), - NewCancelTimers = CancelTimers + 1, - %% Insert the new timer into - %% both TimerRefs and TimerTypes - parse_timers( - TimerRefs#{TimerRef => TimerType}, - {TimerTypes#{TimerType => TimerRef}, - NewCancelTimers}, - TimeoutsR, NewSeen, TimeoutEvents); - {#{} = TimerTypes,CancelTimers} -> - %% Insert the new timer into - %% both TimerRefs and TimerTypes - parse_timers( - TimerRefs#{TimerRef => TimerType}, - {TimerTypes#{TimerType => TimerRef}, - CancelTimers}, - TimeoutsR, NewSeen, TimeoutEvents) - end - end +parse_timeout_opts_abs(Opts, Abs) -> + case Opts of + [] -> + Abs; + [{abs,Abs_1}|Opts] when is_boolean(Abs_1) -> + parse_timeout_opts_abs(Opts, Abs_1); + _ -> + badarg end. %% Enqueue immediate timeout events (timeout 0 events) @@ -1814,62 +2055,94 @@ parse_timers( %% so if there are enqueued events before the event timer %% timeout 0 event - the event timer is cancelled hence no event. %% -%% Other (state_timeout) timeout 0 events that are after -%% the event timer timeout 0 events are considered to +%% Other (state_timeout and {timeout,Name}) timeout 0 events +%% that are after an event timer timeout 0 event are considered to %% belong to timers that were started after the event timer %% timeout 0 event fired, so they do not cancel the event timer. %% -prepend_timeout_events([], EventsR) -> - EventsR; -prepend_timeout_events([{timeout,_} = TimeoutEvent|TimeoutEvents], []) -> - prepend_timeout_events(TimeoutEvents, [TimeoutEvent]); -prepend_timeout_events([{timeout,_}|TimeoutEvents], EventsR) -> +prepend_timeout_events(_P, Debug, _S, [], EventsR) -> + {Debug,EventsR}; +prepend_timeout_events( + P, Debug, S, [{timeout,_} = TimeoutEvent|TimeoutEvents], []) -> + %% Prepend this since there are no other events in queue + case Debug of + ?not_sys_debug -> + prepend_timeout_events( + P, Debug, S, TimeoutEvents, [TimeoutEvent]); + _ -> + {State,_Data} = S#state.state_data, + Debug_1 = + sys_debug( + Debug, P#params.name, {in,TimeoutEvent,State}), + prepend_timeout_events( + P, Debug_1, S, TimeoutEvents, [TimeoutEvent]) + end; +prepend_timeout_events( + P, Debug, S, [{timeout,_}|TimeoutEvents], EventsR) -> %% Ignore since there are other events in queue %% so they have cancelled the event timeout 0. - prepend_timeout_events(TimeoutEvents, EventsR); -prepend_timeout_events([TimeoutEvent|TimeoutEvents], EventsR) -> + prepend_timeout_events(P, Debug, S, TimeoutEvents, EventsR); +prepend_timeout_events( + P, Debug, S, [TimeoutEvent|TimeoutEvents], EventsR) -> %% Just prepend all others - prepend_timeout_events(TimeoutEvents, [TimeoutEvent|EventsR]). + case Debug of + ?not_sys_debug -> + prepend_timeout_events( + P, Debug, S, TimeoutEvents, [TimeoutEvent|EventsR]); + _ -> + {State,_Data} = S#state.state_data, + Debug_1 = + sys_debug( + Debug, P#params.name, {in,TimeoutEvent,State}), + prepend_timeout_events( + P, Debug_1, S, TimeoutEvents, [TimeoutEvent|EventsR]) + end. %%--------------------------------------------------------------------------- %% Server helpers -reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) -> +reply_then_terminate(Class, Reason, Stacktrace, P, Debug, S, Q, Replies) -> do_reply_then_terminate( - Class, Reason, Stacktrace, Debug, S, Q, listify(Replies)). + Class, Reason, Stacktrace, P, Debug, S, Q, listify(Replies)). %% do_reply_then_terminate( - Class, Reason, Stacktrace, Debug, S, Q, []) -> - terminate(Class, Reason, Stacktrace, Debug, S, Q); + Class, Reason, Stacktrace, P, Debug, S, Q, []) -> + terminate(Class, Reason, Stacktrace, P, Debug, S, Q); do_reply_then_terminate( - Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) -> + Class, Reason, Stacktrace, P, Debug, S, Q, [R|Rs]) -> case R of - {reply,{_To,_Tag}=From,Reply} -> - reply(From, Reply), - NewDebug = - ?sys_debug( - Debug, - begin - #state{name = Name, state = State} = S, - {Name,State} - end, - {out,Reply,From}), - do_reply_then_terminate( - Class, Reason, Stacktrace, NewDebug, S, Q, Rs); + {reply,From,Reply} -> + case from(From) of + true -> + reply(From, Reply), + Debug_1 = + ?sys_debug( + Debug, + P#params.name, + {out,Reply,From}), + do_reply_then_terminate( + Class, Reason, Stacktrace, P, Debug_1, S, Q, Rs); + false -> + terminate( + error, + {bad_reply_action_from_state_function,R}, + ?STACKTRACE(), + P, Debug, S, Q) + end; _ -> terminate( error, {bad_reply_action_from_state_function,R}, ?STACKTRACE(), - Debug, S, Q) + P, Debug, S, Q) end. terminate( - Class, Reason, Stacktrace, Debug, - #state{module = Module, state = State, data = Data} = S, - Q) -> + Class, Reason, Stacktrace, + #params{module = Module} = P, Debug, + #state{state_data = {State,Data}} = S, Q) -> case erlang:function_exported(Module, terminate, 3) of true -> try Module:terminate(Reason, State, Data) of @@ -1877,8 +2150,7 @@ terminate( catch _ -> ok; C:R:ST -> - error_info(C, R, ST, S, Q), - sys:print_log(Debug), + error_info(C, R, ST, Debug, P, S, Q), erlang:raise(C, R, ST) end; false -> @@ -1887,14 +2159,13 @@ terminate( _ = case Reason of normal -> - terminate_sys_debug(Debug, S, State, Reason); + terminate_sys_debug(Debug, P, State, Reason); shutdown -> - terminate_sys_debug(Debug, S, State, Reason); + terminate_sys_debug(Debug, P, State, Reason); {shutdown,_} -> - terminate_sys_debug(Debug, S, State, Reason); + terminate_sys_debug(Debug, P, State, Reason); _ -> - error_info(Class, Reason, Stacktrace, S, Q), - sys:print_log(Debug) + error_info(Class, Reason, Stacktrace, Debug, P, S, Q) end, case Stacktrace of [] -> @@ -1903,38 +2174,67 @@ terminate( erlang:raise(Class, Reason, Stacktrace) end. -terminate_sys_debug(Debug, S, State, Reason) -> - ?sys_debug(Debug, {S#state.name,State}, {terminate,Reason}). +terminate_sys_debug(Debug, P, State, Reason) -> + ?sys_debug(Debug, P#params.name, {terminate,Reason,State}). error_info( - Class, Reason, Stacktrace, - #state{ + Class, Reason, Stacktrace, Debug, + #params{ name = Name, callback_mode = CallbackMode, - state_enter = StateEnter, - postponed = P} = S, + state_enter = StateEnter} = P, + #state{postponed = Postponed} = S, Q) -> + Log = sys:get_log(Debug), ?LOG_ERROR(#{label=>{gen_statem,terminate}, name=>Name, queue=>Q, - postponed=>P, + postponed=>Postponed, callback_mode=>CallbackMode, state_enter=>StateEnter, - state=>format_status(terminate, get(), S), - reason=>{Class,Reason,Stacktrace}}, + state=>format_status(terminate, get(), P, S), + log=>Log, + reason=>{Class,Reason,Stacktrace}, + client_info=>client_stacktrace(Q)}, #{domain=>[otp], report_cb=>fun gen_statem:format_log/1, error_logger=>#{tag=>error}}). +client_stacktrace([]) -> + undefined; +client_stacktrace([{{call,{Pid,_Tag}},_Req}|_]) when is_pid(Pid) -> + if + node(Pid) =:= node() -> + case + process_info(Pid, [current_stacktrace, registered_name]) + of + undefined -> + {Pid,dead}; + [{current_stacktrace, Stacktrace}, + {registered_name, []}] -> + {Pid,{Pid,Stacktrace}}; + [{current_stacktrace, Stacktrace}, + {registered_name, Name}] -> + {Pid,{Name,Stacktrace}} + end; + true -> + {Pid,remote} + end; +client_stacktrace([_|_]) -> + undefined. + + format_log(#{label:={gen_statem,terminate}, name:=Name, queue:=Q, - postponed:=P, + postponed:=Postponed, callback_mode:=CallbackMode, state_enter:=StateEnter, state:=FmtData, - reason:={Class,Reason,Stacktrace}}) -> + log:=Log, + reason:={Class,Reason,Stacktrace}, + client_info:=ClientInfo}) -> {FixedReason,FixedStacktrace} = case Stacktrace of [{M,F,Args,_}|ST] @@ -1954,14 +2254,12 @@ format_log(#{label:={gen_statem,terminate}, true -> {Reason,Stacktrace}; false -> - {{'function not exported',{M,F,Arity}}, - ST} + {{'function not exported',{M,F,Arity}},ST} end end; _ -> {Reason,Stacktrace} end, - [LimitedP, LimitedFmtData, LimitedFixedReason] = - [error_logger:limit_term(D) || D <- [P, FmtData, FixedReason]], + {ClientFmt,ClientArgs} = format_client_log(ClientInfo), CBMode = case StateEnter of true -> @@ -1981,39 +2279,60 @@ format_log(#{label:={gen_statem,terminate}, [_,_|_] -> "** Queued = ~tp~n"; _ -> "" end ++ - case P of + case Postponed of [] -> ""; _ -> "** Postponed = ~tp~n" end ++ case FixedStacktrace of [] -> ""; _ -> "** Stacktrace =~n** ~tp~n" - end, + end ++ + case Log of + [] -> ""; + _ -> "** Log =~n** ~tp~n" + end ++ ClientFmt, [Name | case Q of [] -> []; - [Event|_] -> [Event] + [Event|_] -> [error_logger:limit_term(Event)] end] ++ - [LimitedFmtData, - Class,LimitedFixedReason, + [error_logger:limit_term(FmtData), + Class,error_logger:limit_term(FixedReason), CBMode] ++ case Q of - [_|[_|_] = Events] -> [Events]; + [_|[_|_] = Events] -> [error_logger:limit_term(Events)]; _ -> [] end ++ - case P of + case Postponed of [] -> []; - _ -> [LimitedP] + _ -> [error_logger:limit_term(Postponed)] end ++ case FixedStacktrace of [] -> []; - _ -> [FixedStacktrace] - end}. + _ -> [error_logger:limit_term(FixedStacktrace)] + end ++ + case Log of + [] -> []; + _ -> [[error_logger:limit_term(T) || T <- Log]] + end ++ ClientArgs}. + +format_client_log(undefined) -> + {"", []}; +format_client_log({Pid,dead}) -> + {"** Client ~p is dead~n", [Pid]}; +format_client_log({Pid,remote}) -> + {"** Client ~p is remote on node ~p~n", [Pid, node(Pid)]}; +format_client_log({_Pid,{Name,Stacktrace}}) -> + {"** Client ~tp stacktrace~n" + "** ~tp~n", + [Name, error_logger:limit_term(Stacktrace)]}. + %% Call Module:format_status/2 or return a default value format_status( Opt, PDict, - #state{module = Module, state = State, data = Data}) -> + #params{module = Module}, + #state{state_data = {State,Data} = State_Data}) -> case erlang:function_exported(Module, format_status, 2) of true -> try Module:format_status(Opt, [PDict,State,Data]) @@ -2021,21 +2340,21 @@ format_status( Result -> Result; _:_ -> format_status_default( - Opt, State, - atom_to_list(Module) ++ ":format_status/2 crashed") + Opt, + {State, + atom_to_list(Module) ++ ":format_status/2 crashed"}) end; false -> - format_status_default(Opt, State, Data) + format_status_default(Opt, State_Data) end. -%% The default Module:format_status/2 -format_status_default(Opt, State, Data) -> - StateData = {State,Data}, +%% The default Module:format_status/3 +format_status_default(Opt, State_Data) -> case Opt of terminate -> - StateData; + State_Data; _ -> - [{data,[{"State",StateData}]}] + [{data,[{"State",State_Data}]}] end. -compile({inline, [listify/1]}). @@ -2044,24 +2363,41 @@ listify(Item) when is_list(Item) -> listify(Item) -> [Item]. + +-define(cancel_timer(TimerRef), + case erlang:cancel_timer(TimerRef) of + false -> + %% No timer found and we have not seen the timeout message + receive + {timeout,(TimerRef),_} -> + ok + end; + _ -> + %% Timer was running + ok + end). + +-compile({inline, [cancel_timer/1]}). +cancel_timer(TimerRef) -> + ?cancel_timer(TimerRef). + %% Cancel timer if running, otherwise no op %% -%% This is an asynchronous cancel so the timer is not really cancelled -%% until we get a cancel_timer msg i.e {cancel_timer,TimerRef,_}. -%% In the mean time we might get a timeout message. -%% -%% Remove the timer from TimerTypes. -%% When we get the cancel_timer msg we remove it from TimerRefs. +%% Remove the timer from Timers -compile({inline, [cancel_timer_by_type/2]}). -cancel_timer_by_type(TimerType, {TimerTypes,CancelTimers} = TT_CT) -> - case TimerTypes of - #{TimerType := TimerRef} -> - ok = erlang:cancel_timer(TimerRef, [{async,true}]), - {maps:remove(TimerType, TimerTypes),CancelTimers + 1}; - #{} -> - TT_CT +cancel_timer_by_type(TimeoutType, {TimerRefs,TimeoutTypes} = Timers) -> + case TimeoutTypes of + #{TimeoutType := TimerRef} -> + ?cancel_timer(TimerRef), + {maps:remove(TimerRef, TimerRefs), + maps:remove(TimeoutType, TimeoutTypes)}; + #{} -> + Timers end. --compile({inline, [cancel_timer/1]}). -cancel_timer(TimerRef) -> - ok = erlang:cancel_timer(TimerRef, [{async,true}]). +-compile({inline, [cancel_timer_by_ref_and_type/4]}). +cancel_timer_by_ref_and_type( + TimerRef, TimeoutType, TimerRefs, TimeoutTypes) -> + ?cancel_timer(TimerRef), + {maps:remove(TimerRef, TimerRefs), + maps:remove(TimeoutType, TimeoutTypes)}. diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl index 6d243e1bec..97ec785c62 100644 --- a/lib/stdlib/src/ms_transform.erl +++ b/lib/stdlib/src/ms_transform.erl @@ -556,8 +556,8 @@ tg({call, Line, {remote,_,{atom,_,erlang},{atom, Line2, FunName}},ParaList}, FunName,length(ParaList)}}) end; tg({call, Line, {remote,_,{atom,_,ModuleName}, - {atom, _, FunName}},_ParaList},B) -> - throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName}}); + {atom, _, FunName}},ParaList},B) -> + throw({error,Line,{?ERR_GENREMOTECALL+B#tgd.eb,ModuleName,FunName,length(ParaList)}}); tg({cons,Line, H, T},B) -> {cons, Line, tg(H,B), tg(T,B)}; tg({nil, Line},_B) -> diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 2cfc702b53..f2b50c354a 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -55,6 +55,14 @@ obsolete_1(erlang, now, 0) -> obsolete_1(calendar, local_time_to_universal_time, 1) -> {deprecated, {calendar, local_time_to_universal_time_dst, 1}}; +%% *** STDLIB added in OTP 22 *** + +obsolete_1(sys, get_debug, 3) -> + {deprecated, + "Deprecated function. " + "Incorrectly documented and in fact only for internal use. " + "Can often be replaced with sys:get_log/1."}; + %% *** STDLIB added in OTP 20 *** obsolete_1(gen_fsm, start, 3) -> diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index 8c0b186288..2a324aef82 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -16,13 +16,38 @@ %% limitations under the License. %% %% %CopyrightEnd% +%% +%% We allow upgrade from, and downgrade to all previous +%% versions from the following OTP releases: +%% - OTP 20 +%% - OTP 21 +%% +%% We also allow upgrade from, and downgrade to all +%% versions that have branched off from the above +%% stated previous versions. +%% {"%VSN%", - %% Up from - max one major revision back - [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.* - {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 - {<<"3\\.6(\\.[0-9]+)*">>,[restart_new_emulator]}],% OTP-21.1 - %% Down to - max one major revision back - [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.* - {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 - {<<"3\\.6(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.1 -}. + [{<<"^3\\.4$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.5$">>,[restart_new_emulator]}, + {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.6$">>,[restart_new_emulator]}, + {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}], + [{<<"^3\\.4$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.5$">>,[restart_new_emulator]}, + {<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}, + {<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}, + {<<"^3\\.6$">>,[restart_new_emulator]}, + {<<"^3\\.6\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}. diff --git a/lib/stdlib/src/sys.erl b/lib/stdlib/src/sys.erl index 0064414d6f..a04195c9ed 100644 --- a/lib/stdlib/src/sys.erl +++ b/lib/stdlib/src/sys.erl @@ -30,7 +30,8 @@ log_to_file/2, log_to_file/3, no_debug/1, no_debug/2, install/2, install/3, remove/2, remove/3]). -export([handle_system_msg/6, handle_system_msg/7, handle_debug/4, - print_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]). + print_log/1, get_log/1, get_debug/3, debug_options/1, suspend_loop_hib/6]). +-deprecated([{get_debug,3,eventually}]). %%----------------------------------------------------------------- %% Types @@ -42,10 +43,17 @@ | {'global', term()} | {'via', module(), term()}. -type system_event() :: {'in', Msg :: _} - | {'in', Msg :: _, From :: _} + | {'in', Msg :: _, State :: _} | {'out', Msg :: _, To :: _} | {'out', Msg :: _, To :: _, State :: _} - | term(). + | {'noreply', State :: _} + | {'continue', Continuation :: _} + | {'code_change', Event :: _, State :: _} + | {'postpone', Event :: _, State :: _, NextState :: _} + | {'consume', Event :: _, State :: _, NextState :: _} + | {'enter', State :: _} + | {'terminate', Reason :: _, State :: _} + | term(). -opaque dbg_opt() :: {'trace', 'true'} | {'log', {N :: non_neg_integer(), @@ -385,31 +393,41 @@ handle_system_msg(SysState, Msg, From, Parent, Mod, Debug, Misc, Hib) -> FormFunc :: format_fun(), Extra :: term(), Event :: system_event(). -handle_debug([{trace, true} | T], FormFunc, State, Event) -> +handle_debug([{trace, true} = DbgOpt | T], FormFunc, State, Event) -> print_event({Event, State, FormFunc}), - [{trace, true} | handle_debug(T, FormFunc, State, Event)]; -handle_debug([{log, {N, LogData}} | T], FormFunc, State, Event) -> - NLogData = [{Event, State, FormFunc} | trim(N, LogData)], - [{log, {N, NLogData}} | handle_debug(T, FormFunc, State, Event)]; -handle_debug([{log_to_file, Fd} | T], FormFunc, State, Event) -> + [DbgOpt | handle_debug(T, FormFunc, State, Event)]; +handle_debug([{log, NLog} | T], FormFunc, State, Event) -> + Item = {Event, State, FormFunc}, + [{log, nlog_put(Item, NLog)} | handle_debug(T, FormFunc, State, Event)]; +handle_debug([{log_to_file, Fd} = DbgOpt | T], FormFunc, State, Event) -> print_event(Fd, {Event, State, FormFunc}), - [{log_to_file, Fd} | handle_debug(T, FormFunc, State, Event)]; + [DbgOpt | handle_debug(T, FormFunc, State, Event)]; handle_debug([{statistics, StatData} | T], FormFunc, State, Event) -> NStatData = stat(Event, StatData), [{statistics, NStatData} | handle_debug(T, FormFunc, State, Event)]; handle_debug([{FuncId, {Func, FuncState}} | T], FormFunc, State, Event) -> - case catch Func(FuncState, Event, State) of + try Func(FuncState, Event, State) of done -> handle_debug(T, FormFunc, State, Event); - {'EXIT', _} -> handle_debug(T, FormFunc, State, Event); NFuncState -> - [{FuncId, {Func, NFuncState}} | handle_debug(T, FormFunc, State, Event)] + [{FuncId, {Func, NFuncState}} | + handle_debug(T, FormFunc, State, Event)] + catch + done -> handle_debug(T, FormFunc, State, Event); + NFuncState -> + [{FuncId, {Func, NFuncState}} | + handle_debug(T, FormFunc, State, Event)]; + _:_ -> handle_debug(T, FormFunc, State, Event) end; handle_debug([{Func, FuncState} | T], FormFunc, State, Event) -> - case catch Func(FuncState, Event, State) of + try Func(FuncState, Event, State) of done -> handle_debug(T, FormFunc, State, Event); - {'EXIT', _} -> handle_debug(T, FormFunc, State, Event); - NFuncState -> + NFuncState -> [{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)] + catch + done -> handle_debug(T, FormFunc, State, Event); + NFuncState -> + [{Func, NFuncState} | handle_debug(T, FormFunc, State, Event)]; + _:_ -> handle_debug(T, FormFunc, State, Event) end; handle_debug([], _FormFunc, _State, _Event) -> []. @@ -526,19 +544,19 @@ debug_cmd({trace, true}, Debug) -> debug_cmd({trace, false}, Debug) -> {ok, remove_debug(trace, Debug)}; debug_cmd({log, true}, Debug) -> - {_N, Logs} = get_debug(log, Debug, {0, []}), - {ok, install_debug(log, {10, trim(10, Logs)}, Debug)}; -debug_cmd({log, {true, N}}, Debug) when is_integer(N), N > 0 -> - {_N, Logs} = get_debug(log, Debug, {0, []}), - {ok, install_debug(log, {N, trim(N, Logs)}, Debug)}; + NLog = get_debug(log, Debug, nlog_new()), + {ok, install_debug(log, nlog_new(NLog), Debug)}; +debug_cmd({log, {true, N}}, Debug) when is_integer(N), 1 =< N -> + NLog = get_debug(log, Debug, nlog_new(N)), + {ok, install_debug(log, nlog_new(N, NLog), Debug)}; debug_cmd({log, false}, Debug) -> {ok, remove_debug(log, Debug)}; debug_cmd({log, print}, Debug) -> print_log(Debug), {ok, Debug}; debug_cmd({log, get}, Debug) -> - {_N, Logs} = get_debug(log, Debug, {0, []}), - {{ok, lists:reverse(Logs)}, Debug}; + NLog = get_debug(log, Debug, nlog_new()), + {{ok, [Event || {Event, _State, _FormFunc} <- nlog_get(NLog)]}, Debug}; debug_cmd({log_to_file, false}, Debug) -> NDebug = close_log_file(Debug), {ok, NDebug}; @@ -595,9 +613,6 @@ stat({out, _Msg, _To}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1}; stat({out, _Msg, _To, _State}, {Time, Reds, In, Out}) -> {Time, Reds, In, Out+1}; stat(_, StatData) -> StatData. -trim(N, LogData) -> - lists:sublist(LogData, 1, N-1). - %%----------------------------------------------------------------- %% Debug structure manipulating functions %%----------------------------------------------------------------- @@ -625,9 +640,14 @@ get_debug2(Item, Debug, Default) -> -spec print_log(Debug) -> 'ok' when Debug :: [dbg_opt()]. print_log(Debug) -> - {_N, Logs} = get_debug(log, Debug, {0, []}), - lists:foreach(fun print_event/1, - lists:reverse(Logs)). + NLog = get_debug(log, Debug, nlog_new()), + lists:foreach(fun print_event/1, nlog_get(NLog)). + +-spec get_log(Debug) -> [system_event()] when + Debug :: [dbg_opt()]. +get_log(Debug) -> + NLog = get_debug(log, Debug, nlog_new()), + [Event || {Event, _State, _FormFunc} <- nlog_get(NLog)]. close_log_file(Debug) -> case get_debug2(log_to_file, Debug, []) of @@ -639,6 +659,74 @@ close_log_file(Debug) -> end. %%----------------------------------------------------------------- +%% Keep the last N Log functions +%%----------------------------------------------------------------- +%% +%% Streamlined Okasaki queue as base for "keep the last N" log. +%% +%% To the reverse list head we cons new items. +%% The forward list contains elements in insertion order, +%% so the head is the oldest and the one to drop off +%% when the log is full. +%% +%% Here is how we can get away with only using one cons cell +%% to wrap the forward and reverse list, and the log size: +%% +%% A full log does not need a counter; we just cons one +%% and drop one: +%% +%% [ReverseList|ForwardList] +%% +%% A non-full log is filling up to N elements; +%% use a down counter instead of a list as first element: +%% +%% [RemainingToFullCount|ReverseList] + +nlog_new() -> + nlog_new(10). +%% +nlog_new([_|_] = NLog) -> + nlog_new(10, NLog); +nlog_new(N) -> + [N]. % Empty log size N >= 1 +%% +nlog_new(N, NLog) -> + lists:foldl( + fun (Item, NL) -> nlog_put(Item, NL) end, + nlog_new(N), + nlog_get(NLog)). + +%% +nlog_put(Item, NLog) -> + case NLog of + [R|FF] when is_list(R) -> + %% Full log + case FF of + [_|F] -> + %% Cons to reverse list, drop from forward list + [[Item|R]|F]; + [] -> + %% Create new forward list from reverse list, + %% create new empty reverse list + [_|F] = lists:reverse(R, [Item]), + [[]|F] + end; + [1|R] -> + %% Log now gets full + [[Item|R]]; + [J|R] -> + %% Filling up to N elements + [J - 1,Item|R] + end. + +nlog_get([[]|F]) -> + F; +nlog_get([[_|_] = R|F]) -> + F ++ lists:reverse(R); +nlog_get([_J|R]) -> + lists:reverse(R). + +%%----------------------------------------------------------------- %% Func: debug_options/1 %% Purpose: Initiate a debug structure. Called by a process that %% wishes to initiate the debug structure without the @@ -665,9 +753,9 @@ debug_options(Options) -> debug_options([trace | T], Debug) -> debug_options(T, install_debug(trace, true, Debug)); debug_options([log | T], Debug) -> - debug_options(T, install_debug(log, {10, []}, Debug)); + debug_options(T, install_debug(log, nlog_new(), Debug)); debug_options([{log, N} | T], Debug) when is_integer(N), N > 0 -> - debug_options(T, install_debug(log, {N, []}, Debug)); + debug_options(T, install_debug(log, nlog_new(N), Debug)); debug_options([statistics | T], Debug) -> debug_options(T, install_debug(statistics, init_stat(), Debug)); debug_options([{log_to_file, FileName} | T], Debug) -> diff --git a/lib/stdlib/test/calendar_SUITE.erl b/lib/stdlib/test/calendar_SUITE.erl index 55118e251c..df62c0921d 100644 --- a/lib/stdlib/test/calendar_SUITE.erl +++ b/lib/stdlib/test/calendar_SUITE.erl @@ -183,14 +183,15 @@ rfc3339(Config) when is_list(Config) -> D = [{time_designator, $\s}], Z = [{offset, "Z"}], - "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12T23:20:50.52Z", Ms), - "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12t23:20:50.52z", Ms), - "1985-04-12T21:20:50.52Z" = + "1985-04-12T23:20:50.520Z" = test_parse("1985-04-12T23:20:50.52Z", Ms), + "1985-04-12T23:20:50.520Z" = test_parse("1985-04-12t23:20:50.52z", Ms), + "1985-04-12T21:20:50.520Z" = test_parse("1985-04-12T23:20:50.52+02:00", Ms), "1985-04-12T23:20:50Z" = test_parse("1985-04-12T23:20:50.52Z", S), - "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12T23:20:50.52Z", Ms), - "1985-04-12T23:20:50.52Z" = test_parse("1985-04-12t23:20:50.52z", Mys), - "1985-04-12 21:20:50.52Z" = + "1985-04-12T23:20:50.520Z" = test_parse("1985-04-12T23:20:50.52Z", Ms), + "1985-04-12T23:20:50.520000Z" = + test_parse("1985-04-12t23:20:50.52z", Mys), + "1985-04-12 21:20:50.520000000Z" = test_parse("1985-04-12 23:20:50.52+02:00", Ns++D), "1985-04-12T23:20:50Z" = test_parse("1985-04-12T23:20:50.52Z"), "1996-12-20T00:39:57Z" = test_parse("1996-12-19T16:39:57-08:00"), @@ -221,17 +222,20 @@ rfc3339(Config) when is_list(Config) -> "1970-01-02T00:00:00Z" = test_parse("1970-01-01T23:59:60Z"), "1970-01-02T00:00:00Z" = test_parse("1970-01-01T23:59:60.5Z"), "1970-01-02T00:00:00Z" = test_parse("1970-01-01T23:59:60.55Z"), - "1970-01-02T00:00:00.55Z" = test_parse("1970-01-01T23:59:60.55Z", Ms), - "1970-01-02T00:00:00.55Z" = test_parse("1970-01-01T23:59:60.55Z", Mys), - "1970-01-02T00:00:00.55Z" = test_parse("1970-01-01T23:59:60.55Z", Ns), + "1970-01-02T00:00:00.550Z" = test_parse("1970-01-01T23:59:60.55Z", Ms), + "1970-01-02T00:00:00.550000Z" = + test_parse("1970-01-01T23:59:60.55Z", Mys), + "1970-01-02T00:00:00.550000000Z" = + test_parse("1970-01-01T23:59:60.55Z", Ns), "1970-01-02T00:00:00.999999Z" = test_parse("1970-01-01T23:59:60.999999Z", Mys), - "1970-01-02T00:00:01Z" = + "1970-01-02T00:00:01.000Z" = test_parse("1970-01-01T23:59:60.999999Z", Ms), "1970-01-01T00:00:00Z" = test_parse("1970-01-01T00:00:00+00:00"), "1970-01-01T00:00:00Z" = test_parse("1970-01-01T00:00:00-00:00"), "1969-12-31T00:01:00Z" = test_parse("1970-01-01T00:00:00+23:59"), - "1918-11-11T09:00:00Z" = test_parse("1918-11-11T11:00:00+02:00", Mys), + "1918-11-11T09:00:00.000000Z" = + test_parse("1918-11-11T11:00:00+02:00", Mys), "1970-01-01T00:00:00.000001Z" = test_parse("1970-01-01T00:00:00.000001Z", Mys), @@ -242,26 +246,26 @@ rfc3339(Config) when is_list(Config) -> test_time(erlang:system_time(millisecond), Ms), test_time(erlang:system_time(microsecond), Mys++[{offset, "-02:20"}]), - T = erlang:system_time(second), - TS = do_format(T, []), - TS = do_format(T * 1000, Ms), - TS = do_format(T * 1000 * 1000, Mys), - TS = do_format(T * 1000 * 1000 * 1000, Ns), - 946720800 = TO = do_parse("2000-01-01 10:00:00Z", []), Str = "2000-01-01T10:02:00+00:02", Str = do_format(TO, [{offset, 120}]), - Str = do_format(TO * 1000, [{offset, 120 * 1000}]++Ms), - Str = do_format(TO * 1000 * 1000, [{offset, 120 * 1000 * 1000}]++Mys), - Str = do_format(TO * 1000 * 1000 * 1000, - [{offset, 120 * 1000 * 1000 * 1000}]++Ns), + "2000-01-01T10:02:00.000+00:02" = + do_format(TO * 1000, [{offset, 120 * 1000}]++Ms), + "2000-01-01T10:02:00.000000+00:02" = + do_format(TO * 1000 * 1000, [{offset, 120 * 1000 * 1000}]++Mys), + "2000-01-01T10:02:00.000000000+00:02" = + do_format(TO * 1000 * 1000 * 1000, + [{offset, 120 * 1000 * 1000 * 1000}]++Ns), NStr = "2000-01-01T09:58:00-00:02", NStr = do_format(TO, [{offset, -120}]), - NStr = do_format(TO * 1000, [{offset, -120 * 1000}]++Ms), - NStr = do_format(TO * 1000 * 1000, [{offset, -120 * 1000 * 1000}]++Mys), - NStr = do_format(TO * 1000 * 1000 * 1000, - [{offset, -120 * 1000 * 1000 * 1000}]++Ns), + "2000-01-01T09:58:00.000-00:02" = + do_format(TO * 1000, [{offset, -120 * 1000}]++Ms), + "2000-01-01T09:58:00.000000-00:02" = + do_format(TO * 1000 * 1000, [{offset, -120 * 1000 * 1000}]++Mys), + "2000-01-01T09:58:00.000000000-00:02" = + do_format(TO * 1000 * 1000 * 1000, + [{offset, -120 * 1000 * 1000 * 1000}]++Ns), 543210000 = do_parse("1970-01-01T00:00:00.54321Z", Ns), 54321000 = do_parse("1970-01-01T00:00:00.054321Z", Ns), @@ -278,18 +282,18 @@ rfc3339(Config) when is_list(Config) -> -1613833200000000 = do_parse("1918-11-11T11:00:00+02:00", Mys), -1613833200000000 = do_parse("1918-11-11T09:00:00Z", Mys), - "1970-01-01T00:00:00Z" = do_format_z(0, Mys), + "1970-01-01T00:00:00.000000Z" = do_format_z(0, Mys), "1970-01-01T00:00:01Z" = do_format_z(1, S), "1970-01-01T00:00:00.001Z" = do_format_z(1, Ms), "1970-01-01T00:00:00.000001Z" = do_format_z(1, Mys), "1970-01-01T00:00:00.000000001Z" = do_format_z(1, Ns), - "1970-01-01T00:00:01Z" = do_format_z(1000000, Mys), - "1970-01-01T00:00:00.54321Z" = do_format_z(543210, Mys), + "1970-01-01T00:00:01.000000Z" = do_format_z(1000000, Mys), + "1970-01-01T00:00:00.543210Z" = do_format_z(543210, Mys), "1970-01-01T00:00:00.543Z" = do_format_z(543, Ms), - "1970-01-01T00:00:00.54321Z" = do_format_z(543210000, Ns), - "1970-01-01T00:00:06.54321Z" = do_format_z(6543210, Mys), - "1979-06-21T12:12:12Z" = do_format_z(298815132000000, Mys), - "1918-11-11T13:00:00Z" = do_format_z(-1613818800000000, Mys), + "1970-01-01T00:00:00.543210000Z" = do_format_z(543210000, Ns), + "1970-01-01T00:00:06.543210Z" = do_format_z(6543210, Mys), + "1979-06-21T12:12:12.000000Z" = do_format_z(298815132000000, Mys), + "1918-11-11T13:00:00.000000Z" = do_format_z(-1613818800000000, Mys), ok. %% diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl index c1613a7273..e791da48cf 100644 --- a/lib/stdlib/test/erl_lint_SUITE.erl +++ b/lib/stdlib/test/erl_lint_SUITE.erl @@ -67,7 +67,8 @@ record_errors/1, otp_11879_cont/1, non_latin1_module/1, otp_14323/1, stacktrace_syntax/1, - otp_14285/1, otp_14378/1]). + otp_14285/1, otp_14378/1, + external_funs/1,otp_15456/1]). suite() -> [{ct_hooks,[ts_install_cth]}, @@ -88,7 +89,8 @@ all() -> maps, maps_type, maps_parallel_match, otp_11851, otp_11879, otp_13230, record_errors, otp_11879_cont, non_latin1_module, otp_14323, - stacktrace_syntax, otp_14285, otp_14378]. + stacktrace_syntax, otp_14285, otp_14378, external_funs, + otp_15456]. groups() -> [{unused_vars_warn, [], @@ -2118,6 +2120,61 @@ otp_5362(Config) when is_list(Config) -> [] = run(Config, Ts), ok. +%% OTP-15456. All compiler options can now be given in the option list +%% (as opposed to only in files). +otp_15456(Config) when is_list(Config) -> + Ts = [ + %% {nowarn_deprecated_function,[{M,F,A}]} can now be given + %% in the option list as well as in an attribute. + %% Wherever it occurs, it is not affected by + %% warn_deprecated_function. + {otp_15456_1, + <<"-compile({nowarn_deprecated_function,{erlang,now,0}}). + -export([foo/0]). + + foo() -> + {erlang:now(), random:seed0(), random:seed(1, 2, 3), + random:uniform(), random:uniform(42)}. + ">>, + {[{nowarn_deprecated_function,{random,seed0,0}}, + {nowarn_deprecated_function,[{random,uniform,0}, + {random,uniform,1}]}, + %% There should be no warnings when attempting to + %% turn of warnings for functions that are not + %% deprecated or not used in the module. + {nowarn_deprecated_function,{random,uniform_s,1}}, + {nowarn_deprecated_function,{erlang,abs,1}}, + warn_deprecated_function]}, + {warnings,[{5,erl_lint, + {deprecated,{random,seed,3}, + "the 'random' module is deprecated; " + "use the 'rand' module instead"}}]}}, + + %% {nowarn_unused_function,[{M,F,A}]} can be given + %% in the option list as well as in an attribute. + %% It was incorrectly documented to only work when + %% given in an attribute. + {otp_15456_2, + <<"-compile({nowarn_unused_function,foo/0}). + foo() -> ok. + bar() -> ok. + foobar() -> ok. + barf(_) -> ok. + other() -> ok. + ">>, + {[{nowarn_unused_function,[{bar,0},{foobar,0}]}, + {nowarn_unused_function,{barf,1}}, + %% There should be no warnings when attempting to + %% turn of warnings for unused functions that are not + %% defined in the module. + {nowarn_unused_function,{not_defined_in_module,1}}, + warn_unused_function]}, + {warnings,[{6,erl_lint,{unused_function,{other,0}}}]} + }], + + [] = run(Config, Ts), + ok. + %% OTP-5371. Aliases for bit syntax expressions are no longer allowed. otp_5371(Config) when is_list(Config) -> Ts = [{otp_5371_1, @@ -4134,6 +4191,21 @@ otp_14285(Config) -> run(Config, Ts), ok. +external_funs(Config) when is_list(Config) -> + Ts = [{external_funs_1, + %% ERL-762: Unused variable warning not being emitted. + <<"f() -> + BugVar = process_info(self()), + if true -> fun m:f/1 end. + f(M, F) -> + BugVar = process_info(self()), + if true -> fun M:F/1 end.">>, + [], + {warnings,[{2,erl_lint,{unused_var,'BugVar'}}, + {5,erl_lint,{unused_var,'BugVar'}}]}}], + run(Config, Ts), + ok. + format_error(E) -> lists:flatten(erl_lint:format_error(E)). diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl index a8264e5a84..62d9d0e0ae 100644 --- a/lib/stdlib/test/gen_fsm_SUITE.erl +++ b/lib/stdlib/test/gen_fsm_SUITE.erl @@ -521,14 +521,16 @@ error_format_status(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), StateData = "called format_status", + Parent = self(), {ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []), %% bad return value in the gen_fsm loop {'EXIT',{{bad_return_value, badreturn},_}} = (catch gen_fsm:sync_send_event(Pid, badreturn)), receive {error,_GroupLeader,{Pid, - "** State machine"++_, - [Pid,{_,_,badreturn},idle,{formatted,StateData},_]}} -> + "** State machine "++_, + [Pid,badreturn,Parent,idle,{formatted,StateData}, + {bad_return_value,badreturn}|_]}} -> ok; Other -> io:format("Unexpected: ~p", [Other]), @@ -541,12 +543,14 @@ terminate_crash_format(Config) when is_list(Config) -> error_logger_forwarder:register(), OldFl = process_flag(trap_exit, true), StateData = crash_terminate, + Parent = self(), {ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []), stop_it(Pid), receive {error,_GroupLeader,{Pid, - "** State machine"++_, - [Pid,{_,_,_},idle,{formatted, StateData},_]}} -> + "** State machine "++_, + [Pid,stop,Parent,idle,{formatted, StateData}, + {crash,terminate}|_]}} -> ok; Other -> io:format("Unexpected: ~p", [Other]), diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 017939fdd6..16cf8f43f9 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -513,7 +513,9 @@ abnormal1dirty(Config) -> %% trap exit since we must link to get the real bad_return_ error abnormal2(Config) -> OldFl = process_flag(trap_exit, true), - {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + {ok,Pid} = + gen_statem:start_link( + ?MODULE, start_arg(Config, []), [{debug,[log]}]), %% bad return value in the gen_statem loop {{{bad_return_from_state_function,badreturn},_},_} = @@ -531,7 +533,9 @@ abnormal2(Config) -> %% trap exit since we must link to get the real bad_return_ error abnormal3(Config) -> OldFl = process_flag(trap_exit, true), - {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + {ok,Pid} = + gen_statem:start_link( + ?MODULE, start_arg(Config, []), [{debug,[log]}]), %% bad return value in the gen_statem loop {{{bad_action_from_state_function,badaction},_},_} = @@ -549,7 +553,9 @@ abnormal3(Config) -> %% trap exit since we must link to get the real bad_return_ error abnormal4(Config) -> OldFl = process_flag(trap_exit, true), - {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + {ok,Pid} = + gen_statem:start_link( + ?MODULE, start_arg(Config, []), [{debug,[log]}]), %% bad return value in the gen_statem loop BadTimeout = {badtimeout,4711,ouch}, @@ -689,7 +695,10 @@ state_enter(_Config) -> end}, {ok,STM} = gen_statem:start_link( - ?MODULE, {map_statem,Machine,[state_enter]}, [{debug,[trace]}]), + ?MODULE, {map_statem,Machine,[state_enter]}, + [{debug,[trace,{log,17}]}]), + ok = sys:log(STM, false), + ok = sys:log(STM, true), [{1,enter,start,start}] = flush(), {2,echo,start} = gen_statem:call(STM, echo), @@ -708,6 +717,11 @@ state_enter(_Config) -> {13,internal,start,wait}] = flush(), {14,repeat,start} = gen_statem:call(STM, repeat), [{15,enter,start,start}] = flush(), + + {ok,Log} = sys:log(STM, get), + io:format("sys:log ~p~n", [Log]), + ok = sys:log(STM, print), + {16,stop} = gen_statem:call(STM, {stop,bye}), [{'EXIT',STM,bye}] = flush(), @@ -1470,7 +1484,8 @@ enter_loop(_Config) -> %% Locally registered process + {local,Name} {ok,Pid1a} = - proc_lib:start_link(?MODULE, enter_loop, [local,local]), + proc_lib:start_link( + ?MODULE, enter_loop, [local,local,[{debug,[{log,7}]}]]), yes = gen_statem:call(Pid1a, 'alive?'), stopped = gen_statem:call(Pid1a, stop), receive @@ -1482,7 +1497,8 @@ enter_loop(_Config) -> %% Unregistered process + {local,Name} {ok,Pid1b} = - proc_lib:start_link(?MODULE, enter_loop, [anon,local]), + proc_lib:start_link( + ?MODULE, enter_loop, [anon,local,[{debug,[log]}]]), receive {'EXIT',Pid1b,process_not_registered} -> ok @@ -1591,6 +1607,9 @@ enter_loop(_Config) -> ok = verify_empty_msgq(). enter_loop(Reg1, Reg2) -> + enter_loop(Reg1, Reg2, []). +%% +enter_loop(Reg1, Reg2, Opts) -> process_flag(trap_exit, true), case Reg1 of local -> register(armitage, self()); @@ -1602,15 +1621,15 @@ enter_loop(Reg1, Reg2) -> case Reg2 of local -> gen_statem:enter_loop( - ?MODULE, [], state0, [], {local,armitage}); + ?MODULE, Opts, state0, [], {local,armitage}); global -> gen_statem:enter_loop( - ?MODULE, [], state0, [], {global,armitage}); + ?MODULE, Opts, state0, [], {global,armitage}); via -> gen_statem:enter_loop( - ?MODULE, [], state0, [], {via, dummy_via, armitage}); + ?MODULE, Opts, state0, [], {via, dummy_via, armitage}); anon -> - gen_statem:enter_loop(?MODULE, [], state0, []) + gen_statem:enter_loop(?MODULE, Opts, state0, []) end. undef_code_change(_Config) -> @@ -1642,7 +1661,9 @@ undef_terminate2(_Config) -> undef_in_terminate(_Config) -> Data = {undef_in_terminate, {?MODULE, terminate}}, - {ok, Statem} = gen_statem:start(?MODULE, {data, Data}, []), + {ok, Statem} = + gen_statem:start( + ?MODULE, {data, Data}, [{debug,[log]}]), try gen_statem:stop(Statem), ct:fail(should_crash) diff --git a/lib/stdlib/test/stdlib_bench_SUITE.erl b/lib/stdlib/test/stdlib_bench_SUITE.erl index b937eeb06a..3456442088 100644 --- a/lib/stdlib/test/stdlib_bench_SUITE.erl +++ b/lib/stdlib/test/stdlib_bench_SUITE.erl @@ -38,7 +38,9 @@ groups() -> [norm_nfc_list, norm_nfc_deep_l, norm_nfc_binary, string_lexemes_list, string_lexemes_binary ]}, - {binary, [{repeat, 5}], + %% Only run 1 binary match repeat as it is very slow pre OTP-22. + %% The results seem to be stable enough anyway + {binary, [{repeat, 1}], [match_single_pattern_no_match, matches_single_pattern_no_match, matches_single_pattern_eventual_match, @@ -65,9 +67,9 @@ cases(gen_server) -> [simple, simple_timer, simple_mon, simple_timer_mon, generic, generic_timer]; cases(gen_statem) -> - [generic, generic_fsm, generic_fsm_transit, - generic_statem, generic_statem_transit, - generic_statem_complex]. + [generic, generic_log, generic_log100, generic_fsm, generic_fsm_transit, + generic_statem, generic_statem_log, generic_statem_log100, + generic_statem_transit, generic_statem_complex]. init_per_group(gen_server, Config) -> compile_servers(Config), @@ -164,19 +166,19 @@ norm_data(Config) -> match_single_pattern_no_match(_Config) -> Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsalp">>, 1000000), - comment(test(binary, match, [Binary, <<"o">>])). + comment(test(100, binary, match, [Binary, <<"o">>])). matches_single_pattern_no_match(_Config) -> Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsalp">>, 1000000), - comment(test(binary, matches, [Binary, <<"o">>])). + comment(test(100, binary, matches, [Binary, <<"o">>])). matches_single_pattern_eventual_match(_Config) -> Binary = binary:copy(<<"ugbcfuysabfuqyfikgfsdalpaskfhgjsdgfjwsal\n">>, 1000000), - comment(test(binary, matches, [Binary, <<"\n">>])). + comment(test(100, binary, matches, [Binary, <<"\n">>])). matches_single_pattern_frequent_match(_Config) -> Binary = binary:copy(<<"abc\n">>, 1000000), - comment(test(binary, matches, [Binary, <<"abc">>])). + comment(test(100, binary, matches, [Binary, <<"abc">>])). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -233,9 +235,11 @@ list() -> random_byte_list(?SIZE). test(Mod, Fun, Args) -> - F = fun() -> loop(?N, Mod, Fun, Args) end, + test(?N, Mod, Fun, Args). +test(Iter, Mod, Fun, Args) -> + F = fun() -> loop(Iter, Mod, Fun, Args) end, {Time, ok} = timer:tc(fun() -> lspawn(F) end), - report_mfa(Time, Mod). + report_mfa(Iter, Time, Mod). loop(0, _M, _F, _A) -> garbage_collect(), ok; loop(N, M, F, A) -> @@ -248,8 +252,8 @@ lspawn(Fun) -> {'DOWN', Ref, process, Pid, Rep} -> Rep end. -report_mfa(Time, Mod) -> - Tps = round((?N*1000000)/Time), +report_mfa(Iter, Time, Mod) -> + Tps = round((Iter*1000000)/Time), ct_event:notify(#event{name = benchmark_data, data = [{suite, "stdlib_" ++ atom_to_list(Mod)}, {value, Tps}]}), @@ -290,12 +294,24 @@ simple_timer_mon(Config) when is_list(Config) -> generic(Config) when is_list(Config) -> comment(do_tests(generic, single_small, Config)). +generic_log(Config) when is_list(Config) -> + comment(do_tests(generic_log, single_small, Config)). + +generic_log100(Config) when is_list(Config) -> + comment(do_tests(generic_log100, single_small, Config)). + generic_timer(Config) when is_list(Config) -> comment(do_tests(generic_timer, single_small, Config)). generic_statem(Config) when is_list(Config) -> comment(do_tests(generic_statem, single_small, Config)). +generic_statem_log(Config) when is_list(Config) -> + comment(do_tests(generic_statem_log, single_small, Config)). + +generic_statem_log100(Config) when is_list(Config) -> + comment(do_tests(generic_statem_log100, single_small, Config)). + generic_statem_transit(Config) when is_list(Config) -> comment(do_tests(generic_statem_transit, single_small, Config)). @@ -367,9 +383,9 @@ norm(T, Ref) -> do_tests(Test, ParamSet, Config) -> BenchmarkSuite = ?config(benchmark_suite, Config), - {Client, ServerMod} = bench(Test), + {Client, ServerMod, ServerArg} = bench(Test), {Parallelism, Message} = bench_params(ParamSet), - Fun = create_clients(Message, ServerMod, Client, Parallelism), + Fun = create_clients(Message, ServerMod, ServerArg, Client, Parallelism), {TotalLoops, AllPidTime} = run_test(Fun), try ?CALLS_PER_LOOP * round((1000 * TotalLoops) / AllPidTime) of PerSecond -> @@ -484,27 +500,35 @@ generic_fsm_transit_client(N, M, P) -> generic_fsm_transit_client(N+1, M, P). bench(simple) -> - {fun simple_client/3, simple_server}; + {fun simple_client/3, simple_server, term}; bench(simple_timer) -> - {fun simple_client_timer/3, simple_server_timer}; + {fun simple_client_timer/3, simple_server_timer, term}; bench(simple_mon) -> - {fun simple_client_mon/3, simple_server_mon}; + {fun simple_client_mon/3, simple_server_mon, term}; bench(simple_timer_mon) -> - {fun simple_client_timer_mon/3, simple_server_timer_mon}; + {fun simple_client_timer_mon/3, simple_server_timer_mon, term}; bench(generic) -> - {fun generic_client/3, generic_server}; + {fun generic_client/3, generic_server, [term]}; +bench(generic_log) -> + {fun generic_client/3, generic_server, [term,{debug,[log]}]}; +bench(generic_log100) -> + {fun generic_client/3, generic_server, [term,{debug,[{log,100}]}]}; bench(generic_timer) -> - {fun generic_timer_client/3, generic_server_timer}; + {fun generic_timer_client/3, generic_server_timer, term}; bench(generic_statem) -> - {fun generic_statem_client/3, generic_statem}; + {fun generic_statem_client/3, generic_statem, [term]}; +bench(generic_statem_log) -> + {fun generic_statem_client/3, generic_statem, [term,{debug,[log]}]}; +bench(generic_statem_log100) -> + {fun generic_statem_client/3, generic_statem, [term,{debug,[{log,100}]}]}; bench(generic_statem_transit) -> - {fun generic_statem_transit_client/3, generic_statem}; + {fun generic_statem_transit_client/3, generic_statem, [term]}; bench(generic_statem_complex) -> - {fun generic_statem_complex_client/3, generic_statem_complex}; + {fun generic_statem_complex_client/3, generic_statem_complex, term}; bench(generic_fsm) -> - {fun generic_fsm_client/3, generic_fsm}; + {fun generic_fsm_client/3, generic_fsm, term}; bench(generic_fsm_transit) -> - {fun generic_fsm_transit_client/3, generic_fsm}. + {fun generic_fsm_transit_client/3, generic_fsm, term}. %% -> {Parallelism, MessageTerm} bench_params(single_small) -> {1, small()}; @@ -532,10 +556,9 @@ parallelism() -> _ -> 1 end. -create_clients(M, ServerMod, Client, Parallel) -> +create_clients(M, ServerMod, ServerArg, Client, Parallel) -> fun() -> - State = term, - ServerPid = ServerMod:start(State), + ServerPid = ServerMod:start(ServerArg), PidRefs = [spawn_monitor(fun() -> Client(0, M, ServerPid) end) || _ <- lists:seq(1, Parallel)], timer:sleep(?MAX_TIME), diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl index abd61dcdef..3707d00dee 100644 --- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl +++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_server.erl @@ -8,8 +8,8 @@ -define(GEN_SERVER, gen_server). -start(State) -> - {ok, Pid} = ?GEN_SERVER:start(?MODULE, State, []), +start([State|Opts]) -> + {ok, Pid} = ?GEN_SERVER:start(?MODULE, State, Opts), Pid. init(State) -> diff --git a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl index 2e0491f060..b106619568 100644 --- a/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl +++ b/lib/stdlib/test/stdlib_bench_SUITE_data/generic_statem.erl @@ -28,8 +28,8 @@ %% API -start(Data) -> - {ok, Pid} = gen_statem:start(?MODULE, Data, []), +start([Data|Opts]) -> + {ok, Pid} = gen_statem:start(?MODULE, Data, Opts), Pid. stop(P) -> @@ -52,7 +52,9 @@ init(Data) -> state1({call, From}, {reply, M}, Data) -> {keep_state, Data, {reply, From, M}}; state1({call, From}, {transit, M}, Data) -> - {next_state, state2, Data, {reply, From, M}}. + {next_state, state2, Data, + [{reply, From, M},{state_timeout,5000,5000}]}. state2({call, From}, {transit, M}, Data) -> - {next_state, state1, Data, {reply, From, M}}. + {next_state, state1, Data, + [{reply, From, M},{state_timeout,5000,5000}]}. diff --git a/lib/stdlib/test/unicode_util_SUITE.erl b/lib/stdlib/test/unicode_util_SUITE.erl index 962b307b07..044b4e5834 100644 --- a/lib/stdlib/test/unicode_util_SUITE.erl +++ b/lib/stdlib/test/unicode_util_SUITE.erl @@ -126,17 +126,30 @@ verify_gc(Line0, N, Acc) -> %io:format("Line: ~s~n",[Line]), [Data|_Comments] = string:tokens(Line, "#"), - %io:format("Data: ~w~n",[string:tokens(Data, " \t")]), + %% io:format("Data: ~w~n",[string:tokens(Data, " \t")]), {Str,Res} = gc_test_data(string:tokens(Data, " \t"), [], [[]]), - try - Res = fetch(Str, fun unicode_util:gc/1), - Acc - catch _Cl:{badmatch, Other} -> + %% io:format("InputStr: ~w ~w~n",[Str,unicode:characters_to_binary(Str)]), + case verify_gc(Str, Res, N, Line) andalso + verify_gc(unicode:characters_to_binary(Str), Res, N, Line0) of + true -> Acc; + false -> Acc+1 + end. + +verify_gc({error,_,[CP|_]}=Err, _Res, N, Line) -> + IsSurrogate = 16#D800 =< CP andalso CP =< 16#DFFF, + %% Surrogat is not valid in utf8 encoding only utf16 + IsSurrogate orelse + io:format("~w: ~ts~n Error in unicode:characters_to_binary ~w~n", [N, Line, Err]), + IsSurrogate; +verify_gc(Str, Res, N, Line) -> + try fetch(Str, fun unicode_util:gc/1) of + Res -> true; + Other -> io:format("Failed: ~p~nInput: ~ts~n\t=> ~w |~ts|~n",[N, Line, Str, Str]), io:format("Expected: ~p~n", [Res]), io:format("Got: ~w~n", [Other]), - Acc+1; - Cl:R:Stacktrace -> + false + catch Cl:R:Stacktrace -> io:format("~p: ~ts => |~tp|~n",[N, Line, Str]), io:format("Expected: ~p~n", [Res]), erlang:raise(Cl,R,Stacktrace) diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index caf5ecdbb4..e0217418fe 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.6 +STDLIB_VSN = 3.7 diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index 6906ef1553..6ad9bec2e6 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -1101,8 +1101,9 @@ lay_2(Node, Ctxt) -> Ctxt1 = reset_prec(Ctxt), D1 = lay(erl_syntax:constrained_function_type_body(Node), Ctxt1), + Ctxt2 = Ctxt1#ctxt{clause = undefined}, D2 = lay(erl_syntax:constrained_function_type_argument(Node), - Ctxt1), + Ctxt2), beside(D1, beside(floating(text(" when ")), D2)); @@ -1113,7 +1114,7 @@ lay_2(Node, Ctxt) -> _ -> {"fun(", ")"} end, - Ctxt1 = reset_prec(Ctxt), + Ctxt1 = (reset_prec(Ctxt))#ctxt{clause = undefined}, D1 = case erl_syntax:function_type_arguments(Node) of any_arity -> text("(...)"); diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl index 9dbd0e302a..6b42f7a0a1 100644 --- a/lib/syntax_tools/test/syntax_tools_SUITE.erl +++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl @@ -26,14 +26,14 @@ -export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1, revert_map_type/1, t_abstract_type/1,t_erl_parse_type/1,t_type/1, t_epp_dodger/1, - t_comment_scan/1,t_igor/1,t_erl_tidy/1]). + t_comment_scan/1,t_igor/1,t_erl_tidy/1,t_prettypr/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [app_test,appup_test,smoke_test,revert,revert_map,revert_map_type, t_abstract_type,t_erl_parse_type,t_type,t_epp_dodger, - t_comment_scan,t_igor,t_erl_tidy]. + t_comment_scan,t_igor,t_erl_tidy,t_prettypr]. groups() -> []. @@ -300,6 +300,14 @@ t_comment_scan(Config) when is_list(Config) -> ok = test_comment_scan(Filenames,DataDir), ok. +t_prettypr(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + Filenames = ["type_specs.erl", + "specs_and_funs.erl"], + ok = test_prettypr(Filenames,DataDir,PrivDir), + ok. + test_files(Config) -> DataDir = ?config(data_dir, Config), [ filename:join(DataDir,Filename) || Filename <- test_files() ]. @@ -307,7 +315,8 @@ test_files(Config) -> test_files() -> ["syntax_tools_SUITE_test_module.erl", "syntax_tools_test.erl", - "type_specs.erl"]. + "type_specs.erl", + "specs_and_funs.erl"]. t_igor(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), @@ -359,6 +368,27 @@ test_comment_scan([File|Files],DataDir) -> test_comment_scan(Files,DataDir). +test_prettypr([],_,_) -> ok; +test_prettypr([File|Files],DataDir,PrivDir) -> + Filename = filename:join(DataDir,File), + io:format("Parsing ~p~n", [Filename]), + {ok, Fs0} = epp:parse_file(Filename, [], []), + Fs = erl_syntax:form_list(Fs0), + PP = erl_prettypr:format(Fs, [{paper, 120}, {ribbon, 110}]), + io:put_chars(PP), + OutFile = filename:join(PrivDir, File), + ok = file:write_file(OutFile,iolist_to_binary(PP)), + io:format("Parsing OutFile: ~s~n", [OutFile]), + {ok, Fs2} = epp:parse_file(OutFile, [], []), + case [Error || {error, _} = Error <- Fs2] of + [] -> + ok; + Errors -> + ?t:fail(Errors) + end, + test_prettypr(Files,DataDir,PrivDir). + + test_epp_dodger([], _, _) -> ok; test_epp_dodger([Filename|Files],DataDir,PrivDir) -> io:format("Parsing ~p~n", [Filename]), diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl new file mode 100644 index 0000000000..8dfeaf5a6b --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/specs_and_funs.erl @@ -0,0 +1,18 @@ +-module(specs_and_funs). + +-export([my_apply/3, two/1]). + +%% OTP-15519, ERL-815 + +-spec my_apply(Fun, Arg, fun((A) -> A)) -> Result when + Fun :: fun((Arg) -> Result), + Arg :: any(), + Result :: any(). + +my_apply(Fun, Arg, _) -> + Fun(Arg). + +-spec two(fun((A) -> A)) -> fun((B) -> B). + +two(F) -> + F(fun(X) -> X end). diff --git a/lib/tftp/doc/src/tftp.xml b/lib/tftp/doc/src/tftp.xml index 4ed54bc462..57d64b7379 100644 --- a/lib/tftp/doc/src/tftp.xml +++ b/lib/tftp/doc/src/tftp.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>tftp</module> + <module since="">tftp</module> <modulesummary>Trivial FTP.</modulesummary> <description> <p>Interface module for the <c>tftp</c> application.</p> @@ -172,7 +172,7 @@ <funcs> <func> - <name>change_config(daemons, Options) -> [{Pid, Result}]</name> + <name since="">change_config(daemons, Options) -> [{Pid, Result}]</name> <fsummary>Changes configuration for all daemons. </fsummary> <type> @@ -187,7 +187,7 @@ </func> <func> - <name>change_config(servers, Options) -> [{Pid, Result}]</name> + <name since="">change_config(servers, Options) -> [{Pid, Result}]</name> <fsummary>Changes configuration for all servers. </fsummary> <type> @@ -202,7 +202,7 @@ </func> <func> - <name>change_config(Pid, Options) -> Result</name> + <name since="">change_config(Pid, Options) -> Result</name> <fsummary>Changes configuration for a TFTP daemon, server, or client process.</fsummary> <type> @@ -217,7 +217,7 @@ </func> <func> - <name>info(daemons) -> [{Pid, Options}]</name> + <name since="">info(daemons) -> [{Pid, Options}]</name> <fsummary>Returns information about all daemons.</fsummary> <type> <v>Pid = [pid()]</v> @@ -230,7 +230,7 @@ </func> <func> - <name>info(servers) -> [{Pid, Options}]</name> + <name since="">info(servers) -> [{Pid, Options}]</name> <fsummary>Returns information about all servers.</fsummary> <type> <v>Pid = [pid()]</v> @@ -243,7 +243,7 @@ </func> <func> - <name>info(Pid) -> {ok, Options} | {error, Reason}</name> + <name since="">info(Pid) -> {ok, Options} | {error, Reason}</name> <fsummary>Returns information about a daemon, server, or client process.</fsummary> <type> <v>Options = [option()]</v> @@ -255,7 +255,7 @@ </func> <func> - <name>read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name> + <name since="">read_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name> <fsummary>Reads a (virtual) file from a TFTP server.</fsummary> <type> <v>RemoteFilename = string()</v> @@ -285,7 +285,7 @@ </func> <func> - <name>start(Options) -> {ok, Pid} | {error, Reason}</name> + <name since="">start(Options) -> {ok, Pid} | {error, Reason}</name> <fsummary>Starts a daemon process.</fsummary> <type> <v>Options = [option()]</v> @@ -301,7 +301,7 @@ </func> <func> - <name>write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name> + <name since="">write_file(RemoteFilename, LocalFilename, Options) -> {ok, LastCallbackState} | {error, Reason}</name> <fsummary>Writes a (virtual) file to a TFTP server.</fsummary> <type> <v>RemoteFilename = string()</v> @@ -389,7 +389,7 @@ <funcs> <func> - <name>Module:abort(Code, Text, State) -> ok</name> + <name since="OTP 18.1">Module:abort(Code, Text, State) -> ok</name> <fsummary>Aborts the file transfer.</fsummary> <type> <v>Code = undef | enoent | eacces | enospc</v> @@ -413,7 +413,7 @@ </func> <func> - <name>Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name> + <name since="OTP 18.1">Module:open(Peer, Access, Filename, Mode, SuggestedOptions, State) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name> <fsummary>Opens a file for read or write access.</fsummary> <type> <v>Peer = {PeerType, PeerHost, PeerPort}</v> @@ -448,7 +448,7 @@ </func> <func> - <name>Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name> + <name since="OTP 18.1">Module:prepare(Peer, Access, Filename, Mode, SuggestedOptions, InitialState) -> {ok, AcceptedOptions, NewState} | {error, {Code, Text}}</name> <fsummary>Prepares to open a file on the client side.</fsummary> <type> <v>Peer = {PeerType, PeerHost, PeerPort}</v> @@ -483,7 +483,7 @@ </func> <func> - <name>Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name> + <name since="OTP 18.1">Module:read(State) -> {more, Bin, NewState} | {last, Bin, FileSize} | {error, {Code, Text}}</name> <fsummary>Reads a chunk from the file.</fsummary> <type> <v>State = NewState = term()</v> @@ -510,7 +510,7 @@ </func> <func> - <name>Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name> + <name since="OTP 18.1">Module:write(Bin, State) -> {more, NewState} | {last, FileSize} | {error, {Code, Text}}</name> <fsummary>Writes a chunk to the file.</fsummary> <type> <v>Bin = binary()</v> @@ -549,7 +549,7 @@ <funcs> <func> - <name>Logger:error_msg(Format, Data) -> ok | exit(Reason)</name> + <name since="OTP 18.1">Logger:error_msg(Format, Data) -> ok | exit(Reason)</name> <fsummary>Logs an error message.</fsummary> <type> <v>Format = string()</v> @@ -565,7 +565,7 @@ </func> <func> - <name>Logger:info_msg(Format, Data) -> ok | exit(Reason)</name> + <name since="OTP 18.1">Logger:info_msg(Format, Data) -> ok | exit(Reason)</name> <fsummary>Logs an info message.</fsummary> <type> <v>Format = string()</v> @@ -579,7 +579,7 @@ </func> <func> - <name>Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name> + <name since="OTP 18.1">Logger:warning_msg(Format, Data) -> ok | exit(Reason)</name> <fsummary>Logs a warning message.</fsummary> <type> <v>Format = string()</v> diff --git a/lib/tools/doc/src/cover.xml b/lib/tools/doc/src/cover.xml index 6c6b20aad8..31bf7266c7 100644 --- a/lib/tools/doc/src/cover.xml +++ b/lib/tools/doc/src/cover.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>cover</module> + <module since="">cover</module> <modulesummary>A Coverage Analysis Tool for Erlang</modulesummary> <description> <p>The module <c>cover</c> provides a set of functions for coverage @@ -115,7 +115,7 @@ </description> <funcs> <func> - <name>start() -> {ok,Pid} | {error,Reason}</name> + <name since="">start() -> {ok,Pid} | {error,Reason}</name> <fsummary>Start Cover.</fsummary> <type> <v>Pid = pid()</v> @@ -128,7 +128,7 @@ </desc> </func> <func> - <name>start(Nodes) -> {ok,StartedNodes} | {error,not_main_node}</name> + <name since="">start(Nodes) -> {ok,StartedNodes} | {error,not_main_node}</name> <fsummary>Start Cover on remote nodes.</fsummary> <type> <v>Nodes = StartedNodes = [atom()]</v> @@ -139,10 +139,10 @@ </desc> </func> <func> - <name>compile(ModFiles) -> Result | [Result]</name> - <name>compile(ModFiles, Options) -> Result | [Result]</name> - <name>compile_module(ModFiles) -> Result | [Result]</name> - <name>compile_module(ModFiles, Options) -> Result | [Result]</name> + <name since="">compile(ModFiles) -> Result | [Result]</name> + <name since="">compile(ModFiles, Options) -> Result | [Result]</name> + <name since="">compile_module(ModFiles) -> Result | [Result]</name> + <name since="">compile_module(ModFiles, Options) -> Result | [Result]</name> <fsummary>Compile one or more modules for Cover analysis.</fsummary> <type> <v>ModFiles = ModFile | [ModFile]</v> @@ -176,9 +176,9 @@ </desc> </func> <func> - <name>compile_directory() -> [Result] | {error,Reason}</name> - <name>compile_directory(Dir) -> [Result] | {error,Reason}</name> - <name>compile_directory(Dir, Options) -> [Result] | {error,Reason}</name> + <name since="">compile_directory() -> [Result] | {error,Reason}</name> + <name since="">compile_directory(Dir) -> [Result] | {error,Reason}</name> + <name since="">compile_directory(Dir, Options) -> [Result] | {error,Reason}</name> <fsummary>Compile all modules in a directory for Cover analysis.</fsummary> <type> <v>Dir = string()</v> @@ -199,7 +199,7 @@ </desc> </func> <func> - <name>compile_beam(ModFiles) -> Result | [Result]</name> + <name since="">compile_beam(ModFiles) -> Result | [Result]</name> <fsummary>Compile one or more modules for Cover analysis, using existing beam(s).</fsummary> <type> <v>ModFiles = ModFile | [ModFile]</v> @@ -241,8 +241,8 @@ </desc> </func> <func> - <name>compile_beam_directory() -> [Result] | {error,Reason}</name> - <name>compile_beam_directory(Dir) -> [Result] | {error,Reason}</name> + <name since="">compile_beam_directory() -> [Result] | {error,Reason}</name> + <name since="">compile_beam_directory(Dir) -> [Result] | {error,Reason}</name> <fsummary>Compile all .beam files in a directory for Cover analysis.</fsummary> <type> <v>Dir = string()</v> @@ -260,14 +260,14 @@ </desc> </func> <func> - <name>analyse() -> {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Modules) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Analysis) -> {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Level) -> {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Modules, Analysis) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Modules, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Analysis, Level) -> {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse(Modules, Analysis, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> + <name since="OTP 18.0">analyse() -> {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Modules) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Analysis) -> {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Level) -> {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Modules, Analysis) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Modules, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Analysis, Level) -> {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse(Modules, Analysis, Level) -> OneResult | {result,Ok,Fail} | {error,not_main_node}</name> <fsummary>Analyse one or more Cover compiled modules.</fsummary> <type> <v>Modules = Module | [Module]</v> @@ -305,10 +305,10 @@ </desc> </func> <func> - <name>analyse_to_file() -> {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse_to_file(Modules) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse_to_file(Options) -> {result,Ok,Fail} | {error,not_main_node}</name> - <name>analyse_to_file(Modules,Options) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name> + <name since="OTP 18.0">analyse_to_file() -> {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse_to_file(Modules) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse_to_file(Options) -> {result,Ok,Fail} | {error,not_main_node}</name> + <name since="">analyse_to_file(Modules,Options) -> Answer | {result,Ok,Fail} | {error,not_main_node}</name> <fsummary>Detailed coverage analysis of one or more Cover compiled modules.</fsummary> <type> <v>Modules = Module | [Module]</v> @@ -359,10 +359,10 @@ </desc> </func> <func> - <name>async_analyse_to_file(Module) -> </name> - <name>async_analyse_to_file(Module,Options) -> </name> - <name>async_analyse_to_file(Module, OutFile) -> </name> - <name>async_analyse_to_file(Module, OutFile, Options) -> pid()</name> + <name since="OTP R14B02">async_analyse_to_file(Module) -> </name> + <name since="OTP R14B02">async_analyse_to_file(Module,Options) -> </name> + <name since="OTP R14B02">async_analyse_to_file(Module, OutFile) -> </name> + <name since="OTP R14B02">async_analyse_to_file(Module, OutFile, Options) -> pid()</name> <fsummary>Asynchronous call to analyse_to_file.</fsummary> <type> <v>Module = atom()</v> @@ -384,7 +384,7 @@ </desc> </func> <func> - <name>modules() -> [Module] | {error,not_main_node}</name> + <name since="">modules() -> [Module] | {error,not_main_node}</name> <fsummary>Return all Cover compiled modules.</fsummary> <type> <v>Module = atom()</v> @@ -395,7 +395,7 @@ </desc> </func> <func> - <name>imported_modules() -> [Module] | {error,not_main_node}</name> + <name since="">imported_modules() -> [Module] | {error,not_main_node}</name> <fsummary>Return all modules for which there are imported data.</fsummary> <type> <v>Module = atom()</v> @@ -406,7 +406,7 @@ </desc> </func> <func> - <name>imported() -> [File] | {error,not_main_node}</name> + <name since="">imported() -> [File] | {error,not_main_node}</name> <fsummary>Return all imported files.</fsummary> <type> <v>File = string()</v> @@ -416,7 +416,7 @@ </desc> </func> <func> - <name>which_nodes() -> [Node] | {error,not_main_node}</name> + <name since="">which_nodes() -> [Node] | {error,not_main_node}</name> <fsummary>Return all nodes that are part of the coverage analysis.</fsummary> <type> <v>Node = atom()</v> @@ -428,7 +428,7 @@ </desc> </func> <func> - <name>is_compiled(Module) -> {file,File} | false | {error,not_main_node}</name> + <name since="">is_compiled(Module) -> {file,File} | false | {error,not_main_node}</name> <fsummary>Check if a module is Cover compiled.</fsummary> <type> <v>Module = atom()</v> @@ -442,8 +442,8 @@ </desc> </func> <func> - <name>reset(Module) -></name> - <name>reset() -> ok | {error,not_main_node}</name> + <name since="">reset(Module) -></name> + <name since="">reset() -> ok | {error,not_main_node}</name> <fsummary>Reset coverage data for Cover compiled modules.</fsummary> <type> <v>Module = atom()</v> @@ -458,8 +458,8 @@ </desc> </func> <func> - <name>export(ExportFile)</name> - <name>export(ExportFile,Module) -> ok | {error,Reason}</name> + <name since="">export(ExportFile)</name> + <name since="">export(ExportFile,Module) -> ok | {error,Reason}</name> <fsummary>Reset coverage data for Cover compiled modules.</fsummary> <type> <v>ExportFile = string()</v> @@ -480,7 +480,7 @@ </desc> </func> <func> - <name>import(ExportFile) -> ok | {error,Reason}</name> + <name since="">import(ExportFile) -> ok | {error,Reason}</name> <fsummary>Reset coverage data for Cover compiled modules.</fsummary> <type> <v>ExportFile = string()</v> @@ -504,14 +504,14 @@ </desc> </func> <func> - <name>stop() -> ok | {error,not_main_node}</name> + <name since="">stop() -> ok | {error,not_main_node}</name> <fsummary>Stop Cover.</fsummary> <desc> <p>Stops the Cover server and unloads all Cover compiled code.</p> </desc> </func> <func> - <name>stop(Nodes) -> ok | {error,not_main_node}</name> + <name since="">stop(Nodes) -> ok | {error,not_main_node}</name> <fsummary>Stop Cover on remote nodes.</fsummary> <type> <v>Nodes = [atom()]</v> @@ -523,7 +523,7 @@ </desc> </func> <func> - <name>flush(Nodes) -> ok | {error,not_main_node}</name> + <name since="OTP R16B">flush(Nodes) -> ok | {error,not_main_node}</name> <fsummary>Collect cover data from remote nodes.</fsummary> <type> <v>Nodes = [atom()]</v> diff --git a/lib/tools/doc/src/cprof.xml b/lib/tools/doc/src/cprof.xml index df0acbe617..b6af8b6d28 100644 --- a/lib/tools/doc/src/cprof.xml +++ b/lib/tools/doc/src/cprof.xml @@ -34,7 +34,7 @@ <rev>PA1</rev> <file>cprof.sgml</file> </header> - <module>cprof</module> + <module since="">cprof</module> <modulesummary>A simple Call Count Profiling Tool using breakpoints for minimal runtime performance impact.</modulesummary> <description> <p>The <c>cprof</c> module is used to profile a program @@ -65,10 +65,10 @@ </description> <funcs> <func> - <name>analyse() -> {AllCallCount, ModAnalysisList}</name> - <name>analyse(Limit) -> {AllCallCount, ModAnalysisList}</name> - <name>analyse(Mod) -> ModAnalysis</name> - <name>analyse(Mod, Limit) -> ModAnalysis</name> + <name since="">analyse() -> {AllCallCount, ModAnalysisList}</name> + <name since="">analyse(Limit) -> {AllCallCount, ModAnalysisList}</name> + <name since="">analyse(Mod) -> ModAnalysis</name> + <name since="">analyse(Mod, Limit) -> ModAnalysis</name> <fsummary>Collect and analyse call counters.</fsummary> <type> <v>Limit = integer()</v> @@ -122,7 +122,7 @@ </desc> </func> <func> - <name>pause() -> integer()</name> + <name since="">pause() -> integer()</name> <fsummary>Pause running call count trace for all functions.</fsummary> <desc> <p>Pause call count tracing for all functions in all modules @@ -137,9 +137,9 @@ </desc> </func> <func> - <name>pause(FuncSpec) -> integer()</name> - <name>pause(Mod, Func) -> integer()</name> - <name>pause(Mod, Func, Arity) -> integer()</name> + <name since="">pause(FuncSpec) -> integer()</name> + <name since="">pause(Mod, Func) -> integer()</name> + <name since="">pause(Mod, Func, Arity) -> integer()</name> <fsummary>Pause running call count trace for matching functions.</fsummary> <type> <v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v> @@ -167,10 +167,10 @@ </desc> </func> <func> - <name>restart() -> integer()</name> - <name>restart(FuncSpec) -> integer()</name> - <name>restart(Mod, Func) -> integer()</name> - <name>restart(Mod, Func, Arity) -> integer()</name> + <name since="">restart() -> integer()</name> + <name since="">restart(FuncSpec) -> integer()</name> + <name since="">restart(Mod, Func) -> integer()</name> + <name since="">restart(Mod, Func, Arity) -> integer()</name> <fsummary>Restart existing call counters for matching functions.</fsummary> <type> <v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v> @@ -197,7 +197,7 @@ </desc> </func> <func> - <name>start() -> integer()</name> + <name since="">start() -> integer()</name> <fsummary>Start call count tracing for all functions.</fsummary> <desc> <p>Start call count tracing for all functions in all modules, @@ -212,9 +212,9 @@ </desc> </func> <func> - <name>start(FuncSpec) -> integer()</name> - <name>start(Mod, Func) -> integer()</name> - <name>start(Mod, Func, Arity) -> integer()</name> + <name since="">start(FuncSpec) -> integer()</name> + <name since="">start(Mod, Func) -> integer()</name> + <name since="">start(Mod, Func, Arity) -> integer()</name> <fsummary>Start call count tracing for matching functions.</fsummary> <type> <v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v> @@ -240,7 +240,7 @@ </desc> </func> <func> - <name>stop() -> integer()</name> + <name since="">stop() -> integer()</name> <fsummary>Stop call count tracing for all functions.</fsummary> <desc> <p>Stop call count tracing for all functions in all modules, @@ -255,9 +255,9 @@ </desc> </func> <func> - <name>stop(FuncSpec) -> integer()</name> - <name>stop(Mod, Func) -> integer()</name> - <name>stop(Mod, Func, Arity) -> integer()</name> + <name since="">stop(FuncSpec) -> integer()</name> + <name since="">stop(Mod, Func) -> integer()</name> + <name since="">stop(Mod, Func, Arity) -> integer()</name> <fsummary>Stop call count tracing for matching functions.</fsummary> <type> <v>FuncSpec = Mod | {Mod,Func,Arity}, {FS}</v> diff --git a/lib/tools/doc/src/eprof.xml b/lib/tools/doc/src/eprof.xml index f098b7d39e..c9e4edd991 100644 --- a/lib/tools/doc/src/eprof.xml +++ b/lib/tools/doc/src/eprof.xml @@ -28,7 +28,7 @@ <date></date> <rev></rev> </header> - <module>eprof</module> + <module since="">eprof</module> <modulesummary>A Time Profiling Tool for Erlang</modulesummary> <description> <p>The module <c>eprof</c> provides a set of functions for time @@ -40,7 +40,7 @@ </description> <funcs> <func> - <name>start() -> {ok,Pid} | {error,Reason}</name> + <name since="">start() -> {ok,Pid} | {error,Reason}</name> <fsummary>Start Eprof.</fsummary> <type> <v>Pid = pid()</v> @@ -51,9 +51,9 @@ </desc> </func> <func> - <name>start_profiling(Rootset) -> profiling | {error, Reason}</name> - <name>start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name> - <name>start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name> + <name since="">start_profiling(Rootset) -> profiling | {error, Reason}</name> + <name since="OTP R14B">start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name> + <name since="OTP R16B01">start_profiling(Rootset,Pattern,Options) -> profiling | {error, Reason}</name> <fsummary>Start profiling.</fsummary> <type> <v>Rootset = [atom() | pid()]</v> @@ -79,7 +79,7 @@ </desc> </func> <func> - <name>stop_profiling() -> profiling_stopped | profiling_already_stopped</name> + <name since="">stop_profiling() -> profiling_stopped | profiling_already_stopped</name> <fsummary>Stop profiling.</fsummary> <desc> <p>Stops profiling started with <c>start_profiling/1</c> or @@ -87,14 +87,14 @@ </desc> </func> <func> - <name>profile(Fun) -> profiling | {error, Reason}</name> - <name>profile(Fun, Options) -> profiling | {error, Reason}</name> - <name>profile(Rootset) -> profiling | {error, Reason}</name> - <name>profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name> - <name>profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name> - <name>profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name> - <name>profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name> - <name>profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name> + <name since="">profile(Fun) -> profiling | {error, Reason}</name> + <name since="">profile(Fun, Options) -> profiling | {error, Reason}</name> + <name since="">profile(Rootset) -> profiling | {error, Reason}</name> + <name since="">profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name> + <name since="OTP R14B">profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name> + <name since="">profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name> + <name since="OTP R14B">profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name> + <name since="OTP R16B01">profile(Rootset,Module,Function,Args,Pattern,Options) -> {ok, Value} | {error, Reason}</name> <fsummary>Start profiling.</fsummary> <type> <v>Rootset = [atom() | pid()]</v> @@ -128,9 +128,9 @@ </desc> </func> <func> - <name>analyze() -> ok</name> - <name>analyze(Type) -> ok</name> - <name>analyze(Type,Options) -> ok</name> + <name since="OTP R14B">analyze() -> ok</name> + <name since="OTP R14B">analyze(Type) -> ok</name> + <name since="OTP R14B">analyze(Type,Options) -> ok</name> <fsummary>Display profiling results per process.</fsummary> <type> <v>Type = procs | total</v> @@ -152,7 +152,7 @@ </desc> </func> <func> - <name>log(File) -> ok</name> + <name since="">log(File) -> ok</name> <fsummary>Activate logging of <c>eprof</c>printouts.</fsummary> <type> <v>File = atom() | string()</v> @@ -164,7 +164,7 @@ </desc> </func> <func> - <name>stop() -> stopped</name> + <name since="">stop() -> stopped</name> <fsummary>Stop Eprof.</fsummary> <desc> <p>Stops the Eprof server.</p> diff --git a/lib/tools/doc/src/fprof.xml b/lib/tools/doc/src/fprof.xml index 1fd828d127..4bb8862016 100644 --- a/lib/tools/doc/src/fprof.xml +++ b/lib/tools/doc/src/fprof.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>fprof.sgml</file> </header> - <module>fprof</module> + <module since="">fprof</module> <modulesummary>A Time Profiling Tool using trace to file for minimal runtime performance impact.</modulesummary> <description> <p>This module is used to profile a program @@ -101,7 +101,7 @@ </description> <funcs> <func> - <name>start() -> {ok, Pid} | {error, {already_started, Pid}}</name> + <name since="">start() -> {ok, Pid} | {error, {already_started, Pid}}</name> <fsummary>Starts the <c>fprof</c> server.</fsummary> <type> <v>Pid = pid()</v> @@ -117,14 +117,14 @@ </desc> </func> <func> - <name>stop() -> ok</name> + <name since="">stop() -> ok</name> <fsummary>Same as <c>stop(normal)</c>.</fsummary> <desc> <p>Same as <c>stop(normal)</c>.</p> </desc> </func> <func> - <name>stop(Reason) -> ok</name> + <name since="">stop(Reason) -> ok</name> <fsummary>Stops the <c>fprof</c> server.</fsummary> <type> <v>Reason = term()</v> @@ -149,7 +149,7 @@ </desc> </func> <func> - <name>apply(Func, Args) -> term()</name> + <name since="">apply(Func, Args) -> term()</name> <fsummary>Same as <c>apply(Func, Args, [])</c>.</fsummary> <type> <v>Func = function() | {Module, Function}</v> @@ -162,7 +162,7 @@ </desc> </func> <func> - <name>apply(Module, Function, Args) -> term()</name> + <name since="">apply(Module, Function, Args) -> term()</name> <fsummary>Same as <c>apply({Module, Function}, Args, [])</c>.</fsummary> <type> <v>Args = [term()]</v> @@ -174,7 +174,7 @@ </desc> </func> <func> - <name>apply(Func, Args, OptionList) -> term()</name> + <name since="">apply(Func, Args, OptionList) -> term()</name> <fsummary>Calls <c>erlang:apply(Func, Args)</c>surrounded by<c>trace([start | OptionList])</c>and<c>trace(stop)</c>.</fsummary> <type> <v>Func = function() | {Module, Function}</v> @@ -210,7 +210,7 @@ </desc> </func> <func> - <name>apply(Module, Function, Args, OptionList) -> term()</name> + <name since="">apply(Module, Function, Args, OptionList) -> term()</name> <fsummary>Same as <c>apply({Module, Function}, Args, OptionList)</c>.</fsummary> <type> <v>Module = atom()</v> @@ -228,7 +228,7 @@ </desc> </func> <func> - <name>trace(start, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace(start, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>trace([start, {file, Filename}])</c>.</fsummary> <type> <v>Reason = term()</v> @@ -238,7 +238,7 @@ </desc> </func> <func> - <name>trace(verbose, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace(verbose, Filename) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>trace([start, verbose, {file, Filename}])</c>.</fsummary> <type> <v>Reason = term()</v> @@ -249,7 +249,7 @@ </desc> </func> <func> - <name>trace(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>trace([{OptionName, OptionValue}])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -262,7 +262,7 @@ </desc> </func> <func> - <name>trace(verbose) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace(verbose) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>trace([start, verbose])</c>.</fsummary> <type> <v>Reason = term()</v> @@ -272,7 +272,7 @@ </desc> </func> <func> - <name>trace(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>trace([OptionName])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -283,7 +283,7 @@ </desc> </func> <func> - <name>trace({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>trace([{OptionName, OptionValue}])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -296,7 +296,7 @@ </desc> </func> <func> - <name>trace([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">trace([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Starts or stops tracing.</fsummary> <type> <v>Option = start | stop | {procs, PidSpec} | {procs, [PidSpec]} | verbose | {verbose, bool()} | file | {file, Filename} | {tracer, Tracer}</v> @@ -360,7 +360,7 @@ </desc> </func> <func> - <name>profile() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">profile() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>profile([])</c>.</fsummary> <type> <v>Reason = term()</v> @@ -370,7 +370,7 @@ </desc> </func> <func> - <name>profile(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">profile(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>profile([{OptionName, OptionValue}])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -383,7 +383,7 @@ </desc> </func> <func> - <name>profile(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">profile(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>profile([OptionName])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -394,7 +394,7 @@ </desc> </func> <func> - <name>profile({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">profile({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>profile([{OptionName, OptionValue}])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -407,7 +407,7 @@ </desc> </func> <func> - <name>profile([Option]) -> ok | {ok, Tracer} | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">profile([Option]) -> ok | {ok, Tracer} | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Compiles a trace into raw profile data held by the <c>fprof</c> server.</fsummary> <type> <v>Option = file | {file, Filename} | dump | {dump, Dump} | append | start | stop</v> @@ -465,7 +465,7 @@ </desc> </func> <func> - <name>analyse() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">analyse() -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>analyse([])</c>.</fsummary> <type> <v>Reason = term()</v> @@ -475,7 +475,7 @@ </desc> </func> <func> - <name>analyse(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">analyse(OptionName, OptionValue) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>analyse([{OptionName, OptionValue}])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -488,7 +488,7 @@ </desc> </func> <func> - <name>analyse(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">analyse(OptionName) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>analyse([OptionName])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -499,7 +499,7 @@ </desc> </func> <func> - <name>analyse({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">analyse({OptionName, OptionValue}) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Same as <c>analyse([{OptionName, OptionValue}])</c>.</fsummary> <type> <v>OptionName = atom()</v> @@ -512,7 +512,7 @@ </desc> </func> <func> - <name>analyse([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> + <name since="">analyse([Option]) -> ok | {error, Reason} | {'EXIT', ServerPid, Reason}</name> <fsummary>Analyses raw profile data in the <c>fprof</c> server.</fsummary> <type> <v>Option = dest | {dest, Dest} | append | {cols, Cols} | callers | {callers, bool()} | no_callers | {sort, SortSpec} | totals | {totals, bool()} | details | {details, bool()} | no_details</v> diff --git a/lib/tools/doc/src/instrument.xml b/lib/tools/doc/src/instrument.xml index 79bacb2927..7e9cbaebb0 100644 --- a/lib/tools/doc/src/instrument.xml +++ b/lib/tools/doc/src/instrument.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>instrument.sgml</file> </header> - <module>instrument</module> + <module since="">instrument</module> <modulesummary>Analysis and Utility Functions for Instrumentation</modulesummary> <description> <p>The module <c>instrument</c> contains support for studying the resource @@ -92,7 +92,7 @@ <funcs> <func> - <name name="allocations" arity="0"/> + <name name="allocations" arity="0" since="OTP 21.0"/> <fsummary>Return a summary of all allocations in the system.</fsummary> <desc> <p>Shorthand for @@ -101,7 +101,7 @@ </func> <func> - <name name="allocations" arity="1"/> + <name name="allocations" arity="1" since="OTP 21.0"/> <fsummary>Return a summary of all allocations filtered by allocator type and scheduler id.</fsummary> <desc> @@ -173,7 +173,7 @@ </func> <func> - <name name="carriers" arity="0"/> + <name name="carriers" arity="0" since="OTP 21.0"/> <fsummary>Return a list of all carriers in the system.</fsummary> <desc> <p>Shorthand for @@ -182,7 +182,7 @@ </func> <func> - <name name="carriers" arity="1"/> + <name name="carriers" arity="1" since="OTP 21.0"/> <fsummary>Return a list of all carriers filtered by allocator type and scheduler id.</fsummary> <desc> diff --git a/lib/tools/doc/src/lcnt.xml b/lib/tools/doc/src/lcnt.xml index d2595cdb60..1d434decfc 100644 --- a/lib/tools/doc/src/lcnt.xml +++ b/lib/tools/doc/src/lcnt.xml @@ -34,7 +34,7 @@ <rev>PA1</rev> <file>lcnt.xml</file> </header> - <module>lcnt</module> + <module since="OTP R13B04">lcnt</module> <modulesummary>A runtime system Lock Profiling tool.</modulesummary> <description> <p>The <c>lcnt</c> module is used to profile the internal ethread locks in the @@ -71,7 +71,7 @@ <funcs> <func> - <name>start() -> {ok, Pid} | {error, {already_started, Pid}} </name> + <name since="OTP R13B04">start() -> {ok, Pid} | {error, {already_started, Pid}} </name> <fsummary>Starts the lock profiler server.</fsummary> <type> <v>Pid = pid()</v> @@ -84,7 +84,7 @@ </func> <func> - <name>stop() -> ok</name> + <name since="OTP R13B04">stop() -> ok</name> <fsummary>Stops the lock profiler server.</fsummary> <desc> <p>Stops the lock profiler server.</p> @@ -92,13 +92,13 @@ </func> <func> - <name>collect() -> ok</name> + <name since="OTP R13B04">collect() -> ok</name> <fsummary>Same as <c>collect(node())</c>.</fsummary> <desc><p>Same as <c>collect(node())</c>.</p></desc> </func> <func> - <name>collect(Node) -> ok</name> + <name since="OTP R13B04">collect(Node) -> ok</name> <fsummary>Collects lock statistics from the runtime system.</fsummary> <type> <v>Node = node()</v> @@ -113,13 +113,13 @@ </func> <func> - <name>clear() -> ok</name> + <name since="OTP R13B04">clear() -> ok</name> <fsummary>Same as <c>clear(node())</c>.</fsummary> <desc><p>Same as <c>clear(node())</c>.</p></desc> </func> <func> - <name>clear(Node) -> ok</name> + <name since="OTP R13B04">clear(Node) -> ok</name> <fsummary>Clears the internal lock statistics from runtime system.</fsummary> <type> <v>Node = node()</v> @@ -133,12 +133,12 @@ </desc> </func> <func> - <name>conflicts() -> ok</name> + <name since="OTP R13B04">conflicts() -> ok</name> <fsummary>Same as <c>conflicts([])</c>.</fsummary> <desc><p>Same as <c>conflicts([])</c>.</p></desc> </func> <func> - <name>conflicts([Option]) -> ok</name> + <name since="OTP R13B04">conflicts([Option]) -> ok</name> <fsummary>Prints a list of internal lock counters.</fsummary> <type> <v>Option = {sort, Sort} | {reverse, bool()} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v> @@ -154,14 +154,14 @@ </func> <func> - <name>locations() -> ok</name> + <name since="OTP R13B04">locations() -> ok</name> <fsummary>Same as <c>locations([])</c>.</fsummary> <desc> <p>Same as <c>locations([])</c>.</p> </desc> </func> <func> - <name>locations([Option]) -> ok</name> + <name since="OTP R13B04">locations([Option]) -> ok</name> <fsummary>Prints a list of internal lock counters by source code locations.</fsummary> <type> <v>Option = {sort, Sort} | {thresholds, [Thresholds]} | {print, [Print | {Print, integer()}]} | {max_locks, MaxLocks} | {combine, bool()}</v> @@ -177,12 +177,12 @@ </func> <func> - <name>inspect(Lock) -> ok</name> + <name since="OTP R13B04">inspect(Lock) -> ok</name> <fsummary>Same as <c>inspect(Lock, [])</c>.</fsummary> <desc><p>Same as <c>inspect(Lock, [])</c>.</p></desc> </func> <func> - <name>inspect(Lock, [Option]) -> ok</name> + <name since="OTP R13B04">inspect(Lock, [Option]) -> ok</name> <fsummary>Prints a list of internal lock counters for a specific lock.</fsummary> <type> <v>Lock = Name | {Name, Id | [Id]}</v> @@ -268,7 +268,7 @@ </func> <func> - <name>information() -> ok</name> + <name since="OTP R13B04">information() -> ok</name> <fsummary>Prints lcnt server state and generic information about collected lock statistics.</fsummary> <desc> <p>Prints lcnt server state and generic information about collected lock statistics.</p> @@ -276,7 +276,7 @@ </func> <func> - <name>swap_pid_keys() -> ok</name> + <name since="OTP R13B04">swap_pid_keys() -> ok</name> <fsummary>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</fsummary> <desc> <p>Swaps places on <c>Name</c> and <c>Id</c> space for ports and processes.</p> @@ -284,7 +284,7 @@ </func> <func> - <name>load(Filename) -> ok</name> + <name since="OTP R13B04">load(Filename) -> ok</name> <fsummary>Restores previously saved data to the server.</fsummary> <type> <v>Filename = filename()</v> @@ -295,7 +295,7 @@ </func> <func> - <name>save(Filename) -> ok</name> + <name since="OTP R13B04">save(Filename) -> ok</name> <fsummary>Saves the collected data to file.</fsummary> <type> <v>Filename = filename()</v> @@ -312,7 +312,7 @@ </section> <funcs> <func> - <name>apply(Fun) -> term()</name> + <name since="OTP R13B04">apply(Fun) -> term()</name> <fsummary>Same as <c>apply(Fun, [])</c>.</fsummary> <type> <v>Fun = fun()</v> @@ -322,7 +322,7 @@ </desc> </func> <func> - <name>apply(Fun, Args) -> term()</name> + <name since="OTP R13B04">apply(Fun, Args) -> term()</name> <fsummary>Same as <c>apply(Module, Function, Args)</c>.</fsummary> <type> <v>Fun = fun()</v> @@ -333,7 +333,7 @@ </desc> </func> <func> - <name>apply(Module, Function, Args) -> term()</name> + <name since="OTP R13B04">apply(Module, Function, Args) -> term()</name> <fsummary>Clears counters, applies function and collects the profiling results.</fsummary> <type> <v>Module = atom()</v> @@ -358,12 +358,12 @@ </func> <func> - <name>pid(Id, Serial) -> pid()</name> + <name since="OTP R13B04">pid(Id, Serial) -> pid()</name> <fsummary>Same as <c>pid(node(), Id, Serial)</c>.</fsummary> <desc><p>Same as <c>pid(node(), Id, Serial)</c>.</p></desc> </func> <func> - <name>pid(Node, Id, Serial) -> pid()</name> + <name since="OTP R13B04">pid(Node, Id, Serial) -> pid()</name> <fsummary>Creates a process id with creation 0.</fsummary> <type> <v>Node = node()</v> @@ -376,12 +376,12 @@ </func> <func> - <name>port(Id) -> port()</name> + <name since="OTP R13B04">port(Id) -> port()</name> <fsummary>Same as <c>port(node(), Id)</c>.</fsummary> <desc><p>Same as <c>port(node(), Id)</c>.</p></desc> </func> <func> - <name>port(Node, Id) -> port()</name> + <name since="OTP R13B04">port(Node, Id) -> port()</name> <fsummary>Creates a port id with creation 0.</fsummary> <type> <v>Node = node()</v> @@ -399,12 +399,12 @@ <funcs> <func> - <name>rt_collect() -> [lock_counter_data()]</name> + <name since="OTP R13B04">rt_collect() -> [lock_counter_data()]</name> <fsummary>Same as <c>rt_collect(node())</c>.</fsummary> <desc> <p>Same as <c>rt_collect(node())</c>.</p> </desc> </func> <func> - <name>rt_collect(Node) -> [lock_counter_data()]</name> + <name since="OTP R13B04">rt_collect(Node) -> [lock_counter_data()]</name> <fsummary>Returns a list of raw lock counter data.</fsummary> <type> <v>Node = node()</v> @@ -413,12 +413,12 @@ </func> <func> - <name>rt_clear() -> ok</name> + <name since="OTP R13B04">rt_clear() -> ok</name> <fsummary>Same as <c>rt_clear(node())</c>.</fsummary> <desc> <p>Same as <c>rt_clear(node())</c>.</p> </desc> </func> <func> - <name>rt_clear(Node) -> ok</name> + <name since="OTP R13B04">rt_clear(Node) -> ok</name> <fsummary>Clears the internal counters.</fsummary> <type> <v>Node = node()</v> @@ -427,13 +427,13 @@ </func> <func> - <name>rt_mask() -> [category_atom()]</name> + <name since="OTP 20.1">rt_mask() -> [category_atom()]</name> <fsummary>Same as <c>rt_mask(node())</c>.</fsummary> <desc><p>Same as <c>rt_mask(node())</c>.</p></desc> </func> <func> - <name>rt_mask(Node) -> [category_atom()]</name> + <name since="OTP 20.1">rt_mask(Node) -> [category_atom()]</name> <fsummary>Returns the current lock category mask.</fsummary> <type> <v>Node = node()</v> @@ -447,7 +447,7 @@ </func> <func> - <name>rt_mask(Categories) -> ok | {error, copy_save_enabled}</name> + <name since="OTP 20.1">rt_mask(Categories) -> ok | {error, copy_save_enabled}</name> <fsummary>Same as <c>rt_mask(node(), Categories)</c>.</fsummary> <type> <v>Categories = [atom()]</v> @@ -456,7 +456,7 @@ </func> <func> - <name>rt_mask(Node, Categories) -> ok | {error, copy_save_enabled}</name> + <name since="OTP 20.1">rt_mask(Node, Categories) -> ok | {error, copy_save_enabled}</name> <fsummary>Changes the lock category mask.</fsummary> <type> <v>Node = node()</v> @@ -489,12 +489,12 @@ </func> <func> - <name>rt_opt({Type, bool()}) -> bool()</name> + <name since="OTP R13B04">rt_opt({Type, bool()}) -> bool()</name> <fsummary>Same as <c>rt_opt(node(), {Type, Opt})</c>.</fsummary> <desc> <p>Same as <c>rt_opt(node(), {Type, Opt})</c>.</p> </desc> </func> <func> - <name>rt_opt(Node, {Type, bool()}) -> bool()</name> + <name since="OTP R13B04">rt_opt(Node, {Type, bool()}) -> bool()</name> <fsummary>Changes the lock counter behavior and returns the previous behaviour.</fsummary> <type> <v>Node = node()</v> diff --git a/lib/tools/doc/src/make.xml b/lib/tools/doc/src/make.xml index 123fcd4afc..af2404707f 100644 --- a/lib/tools/doc/src/make.xml +++ b/lib/tools/doc/src/make.xml @@ -30,7 +30,7 @@ <date></date> <rev></rev> </header> - <module>make</module> + <module since="">make</module> <modulesummary>A Make Utility for Erlang</modulesummary> <description> <p>The module <c>make</c> provides a set of functions similar to @@ -38,8 +38,8 @@ </description> <funcs> <func> - <name>all() -> up_to_date | error</name> - <name>all(Options) -> up_to_date | error</name> + <name since="">all() -> up_to_date | error</name> + <name since="">all(Options) -> up_to_date | error</name> <fsummary>Compile a set of modules.</fsummary> <type> <v>Options = [Option]</v> @@ -87,8 +87,8 @@ </desc> </func> <func> - <name>files(ModFiles) -> up_to_date | error</name> - <name>files(ModFiles, Options) -> up_to_date | error</name> + <name since="">files(ModFiles) -> up_to_date | error</name> + <name since="">files(ModFiles, Options) -> up_to_date | error</name> <fsummary>Compile a set of modules.</fsummary> <type> <v>ModFiles = [Module | File]</v> diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index f10953774f..a6781dfdb3 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -31,6 +31,21 @@ </header> <p>This document describes the changes made to the Tools application.</p> +<section><title>Tools 3.0.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove emacs warnings and added more tests.</p> + <p> + Own Id: OTP-15476</p> + </item> + </list> + </section> + +</section> + <section><title>Tools 3.0.1</title> <section><title>Improvements and New Features</title> diff --git a/lib/tools/doc/src/tags.xml b/lib/tools/doc/src/tags.xml index ea0ae5cc4d..90a8b28177 100644 --- a/lib/tools/doc/src/tags.xml +++ b/lib/tools/doc/src/tags.xml @@ -32,7 +32,7 @@ <rev>A</rev> <file>tags.sgml</file> </header> - <module>tags</module> + <module since="">tags</module> <modulesummary>Generate Emacs TAGS file from Erlang source files</modulesummary> <description> <p>A <c>TAGS</c> file is used by Emacs to find function and variable @@ -42,14 +42,14 @@ </description> <funcs> <func> - <name>file(File [, Options])</name> + <name since="">file(File [, Options])</name> <fsummary>Create a <c>TAGS</c>file for the file <c>File</c>.</fsummary> <desc> <p>Create a <c>TAGS</c> file for the file <c>File</c>.</p> </desc> </func> <func> - <name>files(FileList [, Options])</name> + <name since="">files(FileList [, Options])</name> <fsummary>Create a TAGS file for the files in the list<c>FileList</c>.</fsummary> <desc> <p>Create a TAGS file for the files in the list @@ -57,7 +57,7 @@ </desc> </func> <func> - <name>dir(Dir [, Options])</name> + <name since="">dir(Dir [, Options])</name> <fsummary>Create a TAGS file for all files in directory<c>Dir</c>.</fsummary> <desc> <p>Create a TAGS file for all files in directory @@ -65,7 +65,7 @@ </desc> </func> <func> - <name>dirs(DirList [, Options])</name> + <name since="">dirs(DirList [, Options])</name> <fsummary>Create a TAGS file for all files in any directory in<c>DirList</c>.</fsummary> <desc> <p>Create a TAGS file for all files in any directory in @@ -73,7 +73,7 @@ </desc> </func> <func> - <name>subdir(Dir [, Options])</name> + <name since="">subdir(Dir [, Options])</name> <fsummary>Descend recursively down the directory <c>Dir</c>and create a <c>TAGS</c>file based on all files found.</fsummary> <desc> <p>Descend recursively down the directory <c>Dir</c> and @@ -81,7 +81,7 @@ </desc> </func> <func> - <name>subdirs(DirList [, Options])</name> + <name since="">subdirs(DirList [, Options])</name> <fsummary>Descend recursively down all the directories in<c>DirList</c>and create a <c>TAGS</c>file based on all files found.</fsummary> <desc> <p>Descend recursively down all the directories in @@ -90,7 +90,7 @@ </desc> </func> <func> - <name>root([Options])</name> + <name since="">root([Options])</name> <fsummary>Create a <c>TAGS</c>file covering all files in the Erlang distribution.</fsummary> <desc> <p>Create a <c>TAGS</c> file covering all files in diff --git a/lib/tools/doc/src/xref.xml b/lib/tools/doc/src/xref.xml index 6f833246ad..ab3641a52f 100644 --- a/lib/tools/doc/src/xref.xml +++ b/lib/tools/doc/src/xref.xml @@ -32,7 +32,7 @@ <rev>PA1</rev> <file>xref.sgml</file> </header> - <module>xref</module> + <module since="">xref</module> <modulesummary>A Cross Reference Tool for analyzing dependencies between functions, modules, applications and releases.</modulesummary> <description> <p>Xref is a cross reference tool that can be used for finding @@ -729,7 +729,7 @@ xref() = atom() | pid() </pre> </description> <funcs> <func> - <name>add_application(Xref, Directory [, Options]) -> {ok, application()} | Error</name> + <name since="">add_application(Xref, Directory [, Options]) -> {ok, application()} | Error</name> <fsummary>Add the modules of an application.</fsummary> <type> <v>Directory = directory()</v> @@ -761,7 +761,7 @@ xref() = atom() | pid() </pre> </desc> </func> <func> - <name>add_directory(Xref, Directory [, Options]) -> {ok, Modules} | Error</name> + <name since="">add_directory(Xref, Directory [, Options]) -> {ok, Modules} | Error</name> <fsummary>Add the modules in a directory.</fsummary> <type> <v>Directory = directory()</v> @@ -791,7 +791,7 @@ xref() = atom() | pid() </pre> </desc> </func> <func> - <name>add_module(Xref, File [, Options]) -> {ok, module()} | Error</name> + <name since="">add_module(Xref, File [, Options]) -> {ok, module()} | Error</name> <fsummary>Add a module.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -814,7 +814,7 @@ xref() = atom() | pid() </pre> </desc> </func> <func> - <name>add_release(Xref, Directory [, Options]) -> {ok, release()} | Error</name> + <name since="">add_release(Xref, Directory [, Options]) -> {ok, release()} | Error</name> <fsummary>Add the modules of a release.</fsummary> <type> <v>Directory = directory()</v> @@ -849,7 +849,7 @@ xref() = atom() | pid() </pre> </desc> </func> <func> - <name>analyze(Xref, Analysis [, Options]) -> {ok, Answer} | Error</name> + <name since="">analyze(Xref, Analysis [, Options]) -> {ok, Answer} | Error</name> <fsummary>Evaluate a predefined analysis.</fsummary> <type> <v>Analysis = undefined_function_calls | undefined_functions | locals_not_used | exports_not_used | deprecated_function_calls | {deprecated_function_calls, DeprFlag} | deprecated_functions | {deprecated_functions, DeprFlag} | {call, FuncSpec} | {use, FuncSpec} | {module_call, ModSpec} | {module_use, ModSpec} | {application_call, AppSpec} | {application_use, AppSpec} | {release_call, RelSpec} | {release_use, RelSpec}</v> @@ -939,7 +939,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>d(Directory) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name> + <name since="">d(Directory) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name> <fsummary>Check the modules in a directory using the code path.</fsummary> <type> <v>Directory = directory()</v> @@ -979,8 +979,8 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>forget(Xref) -> ok</name> - <name>forget(Xref, Variables) -> ok | Error</name> + <name since="">forget(Xref) -> ok</name> + <name since="">forget(Xref, Variables) -> ok | Error</name> <fsummary>Remove user variables and their values.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -994,7 +994,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>format_error(Error) -> Chars</name> + <name since="">format_error(Error) -> Chars</name> <fsummary>Return an English description of an Xref error reply.</fsummary> <type> <v>Error = {error, module(), term()}</v> @@ -1008,8 +1008,8 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>get_default(Xref) -> [{Option, Value}]</name> - <name>get_default(Xref, Option) -> {ok, Value} | Error</name> + <name since="">get_default(Xref) -> [{Option, Value}]</name> + <name since="">get_default(Xref, Option) -> {ok, Value} | Error</name> <fsummary>Return the default values of options.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1023,7 +1023,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>get_library_path(Xref) -> {ok, LibraryPath}</name> + <name since="">get_library_path(Xref) -> {ok, LibraryPath}</name> <fsummary>Return the library path.</fsummary> <type> <v>LibraryPath = library_path()</v> @@ -1034,9 +1034,9 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>info(Xref) -> [Info]</name> - <name>info(Xref, Category) -> [{Item, [Info]}]</name> - <name>info(Xref, Category, Items) -> [{Item, [Info]}]</name> + <name since="">info(Xref) -> [Info]</name> + <name since="">info(Xref, Category) -> [{Item, [Info]}]</name> + <name since="">info(Xref, Category, Items) -> [{Item, [Info]}]</name> <fsummary>Return information about an Xref server.</fsummary> <type> <v>Application = [] | [application()]</v> @@ -1220,8 +1220,8 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>m(Module) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name> - <name>m(File) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name> + <name since="">m(Module) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name> + <name since="">m(File) -> [DebugInfoResult] | [NoDebugInfoResult] | Error</name> <fsummary>Check a module using the code path.</fsummary> <type> <v>DebugInfoResult = {deprecated, [funcall()]} | {undefined, [funcall()]} | {unused, [mfa()]}</v> @@ -1263,7 +1263,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>q(Xref, Query [, Options]) -> {ok, Answer} | Error</name> + <name since="">q(Xref, Query [, Options]) -> {ok, Answer} | Error</name> <fsummary>Evaluate a query.</fsummary> <type> <v>Answer = false | [constant()] | [Call] | [Component] | int() | [DefineAt] | [CallAt] | [AllLines]</v> @@ -1322,7 +1322,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>remove_application(Xref, Applications) -> ok | Error</name> + <name since="">remove_application(Xref, Applications) -> ok | Error</name> <fsummary>Remove applications and their modules.</fsummary> <type> <v>Applications = application() | [application()]</v> @@ -1335,7 +1335,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>remove_module(Xref, Modules) -> ok | Error</name> + <name since="">remove_module(Xref, Modules) -> ok | Error</name> <fsummary>Remove analyzed modules.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1348,7 +1348,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>remove_release(Xref, Releases) -> ok | Error</name> + <name since="">remove_release(Xref, Releases) -> ok | Error</name> <fsummary>Remove releases and their applications and modules.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1363,7 +1363,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>replace_application(Xref, Application, Directory [, Options]) -> {ok, application()} | Error</name> + <name since="">replace_application(Xref, Application, Directory [, Options]) -> {ok, application()} | Error</name> <fsummary>Replace an application's modules.</fsummary> <type> <v>Application = application()</v> @@ -1384,7 +1384,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>replace_module(Xref, Module, File [, Options]) -> {ok, module()} | Error</name> + <name since="">replace_module(Xref, Module, File [, Options]) -> {ok, module()} | Error</name> <fsummary>Replace an analyzed module.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1409,8 +1409,8 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>set_default(Xref, Option, Value) -> {ok, OldValue} | Error</name> - <name>set_default(Xref, OptionValues) -> ok | Error</name> + <name since="">set_default(Xref, Option, Value) -> {ok, OldValue} | Error</name> + <name since="">set_default(Xref, OptionValues) -> ok | Error</name> <fsummary>Set the default values of options.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1435,7 +1435,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>set_library_path(Xref, LibraryPath [, Options]) -> ok | Error</name> + <name since="">set_library_path(Xref, LibraryPath [, Options]) -> ok | Error</name> <fsummary>Set the library path and finds the library modules.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1469,7 +1469,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>start(NameOrOptions) -> Return</name> + <name since="">start(NameOrOptions) -> Return</name> <fsummary>Create an Xref server.</fsummary> <type> <v>NameOrOptions = Name | Options</v> @@ -1487,7 +1487,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>start(Name, Options) -> Return</name> + <name since="">start(Name, Options) -> Return</name> <fsummary>Create an Xref server.</fsummary> <type> <v>Name = atom()</v> @@ -1504,7 +1504,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>stop(Xref)</name> + <name since="">stop(Xref)</name> <fsummary>Delete an Xref server.</fsummary> <type> <v>Xref = xref()</v> @@ -1514,7 +1514,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>update(Xref [, Options]) -> {ok, Modules} | Error</name> + <name since="">update(Xref [, Options]) -> {ok, Modules} | Error</name> <fsummary>Replace newly compiled analyzed modules.</fsummary> <type> <v>Error = {error, module(), Reason}</v> @@ -1534,7 +1534,7 @@ Evaluates a predefined analysis. </desc> </func> <func> - <name>variables(Xref [, Options]) -> {ok, [VariableInfo]}</name> + <name since="">variables(Xref [, Options]) -> {ok, [VariableInfo]}</name> <fsummary>Return the names of variables.</fsummary> <type> <v>Options = [Option] | Option</v> diff --git a/lib/tools/priv/styles.css b/lib/tools/priv/styles.css index e10e94e3ad..84f00be9fd 100644 --- a/lib/tools/priv/styles.css +++ b/lib/tools/priv/styles.css @@ -53,21 +53,25 @@ table thead { display: none; } table td.line, +table td.line a, table td.hits { width: 20px; background: #eaeaea; text-align: center; + text-decoration: none; font-size: 11px; padding: 0 10px; color: #949494; } table td.hits { width: 10px; + text-align: right; padding: 2px 5px; color: rgba(0, 0, 0, 0.6); background-color: #f0f0f0; } tr.miss td.line, +tr.miss td.line a, tr.miss td.hits { background-color: #ffdce0; border-color: #fdaeb7; @@ -76,6 +80,7 @@ tr.miss td { background-color: #ffeef0; } tr.hit td.line, +tr.hit td.line a, tr.hit td.hits { background-color: #cdffd8; border-color: #bef5cb; diff --git a/lib/tools/src/cover.erl b/lib/tools/src/cover.erl index d7269e3f27..8d4561ca9e 100644 --- a/lib/tools/src/cover.erl +++ b/lib/tools/src/cover.erl @@ -2563,11 +2563,13 @@ table_row(Line, L) -> table_data(Line, L, N) -> LineNoNL = Line -- "\n", ["<td class=\"line\" id=\"L",integer_to_list(L),"\">", + "<a href=\"#L",integer_to_list(L),"\">", integer_to_list(L), - "</td>\n", + "</a></td>\n", "<td class=\"hits\">",maybe_integer_to_list(N),"</td>\n", "<td class=\"source\"><code>",LineNoNL,"</code></td>\n</tr>\n"]. +maybe_integer_to_list(0) -> "<pre style=\"display: inline;\">:-(</pre>"; maybe_integer_to_list(N) when is_integer(N) -> integer_to_list(N); maybe_integer_to_list(_) -> "". diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl index 535ddbcd04..86e3d3a8b8 100644 --- a/lib/tools/src/eprof.erl +++ b/lib/tools/src/eprof.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2017. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -26,11 +26,11 @@ -export([start/0, stop/0, - dump/0, + dump/0, dump_data/0, start_profiling/1, start_profiling/2, start_profiling/3, profile/1, profile/2, profile/3, profile/4, profile/5, stop_profiling/0, - analyze/0, analyze/1, analyze/2, + analyze/0, analyze/1, analyze/2, analyze/4, log/1]). %% Internal exports @@ -117,6 +117,9 @@ profile(Rootset, M, F, A, Pattern, Options) -> dump() -> gen_server:call(?MODULE, dump, infinity). +dump_data() -> + gen_server:call(?MODULE, dump_data, infinity). + log(File) -> gen_server:call(?MODULE, {logfile, File}, infinity). @@ -151,22 +154,18 @@ init([]) -> %% analyze -handle_call({analyze, _, _}, _, #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0} = Bpd } = S) when is_record(Bpd, bpd) -> +handle_call( + {analyze, _, _}, _, + #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0 } } = S) -> {reply, nothing_to_analyze, S}; -handle_call({analyze, procs, Opts}, _, #state{ bpd = #bpd{ p = Ps, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) -> - lists:foreach(fun - ({Pid, Mfas}) -> - {Pn, Pus} = sum_bp_total_n_us(Mfas), - format(Fd, "~n****** Process ~w -- ~s % of profiled time *** ~n", [Pid, s("~.2f", [100.0*divide(Pus,Tus)])]), - print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts), - ok - end, gb_trees:to_list(Ps)), - {reply, ok, S}; +handle_call({analyze, procs, Opts}, _, #state{ bpd = Bpd, fd = Fd } = S) + when is_record(Bpd, bpd) -> + {reply, analyze(Fd, procs, Opts, Bpd), S}; -handle_call({analyze, total, Opts}, _, #state{ bpd = #bpd{ mfa = Mfas, n = Tn, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) -> - print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts), - {reply, ok, S}; +handle_call({analyze, total, Opts}, _, #state{ bpd = Bpd, fd = Fd } = S) + when is_record(Bpd, bpd) -> + {reply, analyze(Fd, total, Opts, Bpd), S}; handle_call({analyze, Type, _Opts}, _, S) -> {reply, {error, {undefined, Type}}, S}; @@ -260,6 +259,10 @@ handle_call({logfile, File}, _From, #state{ fd = OldFd } = S) -> handle_call(dump, _From, #state{ bpd = Bpd } = S) when is_record(Bpd, bpd) -> {reply, gb_trees:to_list(Bpd#bpd.p), S}; +handle_call(dump_data, _, #state{ bpd = #bpd{} = Bpd } = S) + when is_record(Bpd, bpd) -> + {reply, Bpd, S}; + handle_call(stop, _FromTag, S) -> {stop, normal, stopped, S}. @@ -438,6 +441,23 @@ collect_bpdfp(Mfa, Tree, Data) -> {PTno + Ni, PTuso + Time, Ti1} end, {0,0, Tree}, Data). + + +analyze(Fd, procs, Opts, #bpd{ p = Ps, us = Tus }) -> + lists:foreach( + fun + ({Pid, Mfas}) -> + {Pn, Pus} = sum_bp_total_n_us(Mfas), + format( + Fd, + "~n****** Process ~w -- ~s % of profiled time *** ~n", + [Pid, s("~.2f", [100.0*divide(Pus, Tus)])]), + print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts), + ok + end, gb_trees:to_list(Ps)); +analyze(Fd, total, Opts, #bpd{ mfa = Mfas, n = Tn, us = Tus } ) -> + print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts). + %% manipulators sort_mfa(Bpfs, mfa) when is_list(Bpfs) -> lists:sort(fun diff --git a/lib/tools/src/tools.app.src b/lib/tools/src/tools.app.src index f8c6aa22cb..f0e0fc4bec 100644 --- a/lib/tools/src/tools.app.src +++ b/lib/tools/src/tools.app.src @@ -21,11 +21,13 @@ [{description, "DEVTOOLS CXC 138 16"}, {vsn, "%VSN%"}, {modules, [cover, + cprof, eprof, fprof, instrument, lcnt, make, + tags, xref, xref_base, xref_compiler, diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index 1bebb1c421..bb8305e9f1 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 3.0.1 +TOOLS_VSN = 3.0.2 diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl index 70a3530526..f13d5873a0 100644 --- a/lib/wx/api_gen/wx_gen_cpp.erl +++ b/lib/wx/api_gen/wx_gen_cpp.erl @@ -995,8 +995,13 @@ build_ret_types(Type,Ps) -> end, lists:foldl(Calc, Free, Ps). -build_ret(Name,_,#type{base={class,Class},single=true}) -> - w(" rt.addRef(getRef((void *)~s,memenv), \"~s\");~n",[Name,Class]); +build_ret(Name,_D,#type{base={class,Class},single=true}=_T) -> + case Class of + "wxGraphicsContext" -> + w(" rt.addRef(getRef((void *)~s,memenv,8), \"~s\");~n",[Name,Class]); + _ -> + w(" rt.addRef(getRef((void *)~s,memenv), \"~s\");~n",[Name,Class]) + end; build_ret(Name,_,#type{name="wxTreeItemId",single=true}) -> w(" rt.add((wxUIntPtr *) ~s.m_pItem);~n",[Name]); build_ret(Name,_,#type{name="wxTreeItemIdValue",single=true}) -> diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp index a7bac4cf9d..74961b2e5e 100644 --- a/lib/wx/c_src/gen/wxe_funcs.cpp +++ b/lib/wx/c_src/gen/wxe_funcs.cpp @@ -6147,18 +6147,18 @@ case wxGraphicsObject_IsNull: { // wxGraphicsObject::IsNull case wxGraphicsContext_Create_1_1: { // wxGraphicsContext::Create wxWindowDC * dc = (wxWindowDC *) getPtr(bp,memenv); bp += 4; wxGraphicsContext * Result = (wxGraphicsContext*)wxGraphicsContext::Create(*dc); - rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext"); + rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext"); break; } case wxGraphicsContext_Create_1_0: { // wxGraphicsContext::Create wxWindow *window = (wxWindow *) getPtr(bp,memenv); bp += 4; wxGraphicsContext * Result = (wxGraphicsContext*)wxGraphicsContext::Create(window); - rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext"); + rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext"); break; } case wxGraphicsContext_Create_0: { // wxGraphicsContext::Create wxGraphicsContext * Result = (wxGraphicsContext*)wxGraphicsContext::Create(); - rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext"); + rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext"); break; } case wxGraphicsContext_CreatePen: { // wxGraphicsContext::CreatePen @@ -6999,7 +6999,7 @@ case wxGraphicsRenderer_CreateContext_1_1: { // wxGraphicsRenderer::CreateContex wxWindowDC * dc = (wxWindowDC *) getPtr(bp,memenv); bp += 4; if(!This) throw wxe_badarg(0); wxGraphicsContext * Result = (wxGraphicsContext*)This->CreateContext(*dc); - rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext"); + rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext"); break; } case wxGraphicsRenderer_CreateContext_1_0: { // wxGraphicsRenderer::CreateContext @@ -7007,7 +7007,7 @@ case wxGraphicsRenderer_CreateContext_1_0: { // wxGraphicsRenderer::CreateContex wxWindow *window = (wxWindow *) getPtr(bp,memenv); bp += 4; if(!This) throw wxe_badarg(0); wxGraphicsContext * Result = (wxGraphicsContext*)This->CreateContext(window); - rt.addRef(getRef((void *)Result,memenv), "wxGraphicsContext"); + rt.addRef(getRef((void *)Result,memenv,8), "wxGraphicsContext"); break; } diff --git a/lib/wx/c_src/wxe_helpers.cpp b/lib/wx/c_src/wxe_helpers.cpp index d1f607d2af..47955494f9 100644 --- a/lib/wx/c_src/wxe_helpers.cpp +++ b/lib/wx/c_src/wxe_helpers.cpp @@ -101,7 +101,7 @@ wxeCommand * wxeFifo::Peek(unsigned int *i) } -void wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd) +int wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd) { unsigned int pos; wxeCommand *curr; @@ -144,6 +144,7 @@ void wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd) } else { // No-op only PING currently curr->buffer = NULL; } + return m_n; } void wxeFifo::Append(wxeCommand *orig) diff --git a/lib/wx/c_src/wxe_helpers.h b/lib/wx/c_src/wxe_helpers.h index 70ffccdc13..a6c00e5aca 100644 --- a/lib/wx/c_src/wxe_helpers.h +++ b/lib/wx/c_src/wxe_helpers.h @@ -63,7 +63,7 @@ class wxeFifo { wxeFifo(unsigned int size); virtual ~wxeFifo(); - void Add(int fc, char * cbuf,int buflen, wxe_data *); + int Add(int fc, char * cbuf,int buflen, wxe_data *); void Append(wxeCommand *Other); wxeCommand * Get(); diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp index 1510866f09..bd22502d00 100644 --- a/lib/wx/c_src/wxe_impl.cpp +++ b/lib/wx/c_src/wxe_impl.cpp @@ -70,7 +70,7 @@ void push_command(int op,char * buf,int len, wxe_data *sd) /* fprintf(stderr, "Op %d %d [%ld] %d\r\n", op, (int) driver_caller(sd->port_handle), wxe_batch->size(), wxe_batch_caller),fflush(stderr); */ erl_drv_mutex_lock(wxe_batch_locker_m); - wxe_queue->Add(op, buf, len, sd); + int n = wxe_queue->Add(op, buf, len, sd); if(wxe_needs_signal) { // wx-thread is waiting on batch end in cond_wait @@ -79,7 +79,7 @@ void push_command(int op,char * buf,int len, wxe_data *sd) } else { // wx-thread is waiting gui-events erl_drv_mutex_unlock(wxe_batch_locker_m); - wxWakeUpIdle(); + if(n < 2) wxWakeUpIdle(); } } @@ -591,7 +591,7 @@ int WxeApp::newPtr(void * ptr, int type, wxeMemEnv *memenv) { return ref; } -int WxeApp::getRef(void * ptr, wxeMemEnv *memenv) { +int WxeApp::getRef(void * ptr, wxeMemEnv *memenv, int type) { if(!ptr) return 0; // NULL and zero is the same ptrMap::iterator it = ptr2ref.find(ptr); if(it != ptr2ref.end()) { @@ -618,7 +618,7 @@ int WxeApp::getRef(void * ptr, wxeMemEnv *memenv) { } memenv->ref2ptr[ref] = ptr; - ptr2ref[ptr] = new wxeRefData(ref, 0, false, memenv); + ptr2ref[ptr] = new wxeRefData(ref, type, false, memenv); return ref; } diff --git a/lib/wx/c_src/wxe_impl.h b/lib/wx/c_src/wxe_impl.h index 140a2bd36a..69cc81c429 100644 --- a/lib/wx/c_src/wxe_impl.h +++ b/lib/wx/c_src/wxe_impl.h @@ -84,7 +84,7 @@ public: wxeMemEnv * getMemEnv(ErlDrvTermData port); int newPtr(void * ptr, int type, wxeMemEnv *memenv); - int getRef(void * ptr, wxeMemEnv *memenv); + int getRef(void * ptr, wxeMemEnv *memenv, int type = 0); void * getPtr(char * bp, wxeMemEnv *memenv); void clearPtr(void *ptr); wxeRefData * getRefData(void *ptr); diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml index a925cf30d4..1061e73138 100644 --- a/lib/wx/doc/src/notes.xml +++ b/lib/wx/doc/src/notes.xml @@ -32,6 +32,22 @@ <p>This document describes the changes made to the wxErlang application.</p> +<section><title>Wx 1.8.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed delayed delete bug which caused wx applications to + crash on Mojave.</p> + <p> + Own Id: OTP-15426 Aux Id: ERL-755 </p> + </item> + </list> + </section> + +</section> + <section><title>Wx 1.8.5</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk index e539ad36f6..d241a7a1b4 100644 --- a/lib/wx/vsn.mk +++ b/lib/wx/vsn.mk @@ -1 +1 @@ -WX_VSN = 1.8.5 +WX_VSN = 1.8.6 diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml index a97036127e..7f6874e36b 100644 --- a/lib/xmerl/doc/src/notes.xml +++ b/lib/xmerl/doc/src/notes.xml @@ -32,6 +32,21 @@ <p>This document describes the changes made to the Xmerl application.</p> +<section><title>Xmerl 1.3.19</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The charset detection parsing crash in some cases when + the XML directive is not syntactic correct.</p> + <p> + Own Id: OTP-15492 Aux Id: ERIERL-283 </p> + </item> + </list> + </section> + +</section> + <section><title>Xmerl 1.3.18</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -62,6 +77,21 @@ </section> +<section><title>Xmerl 1.3.16.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The charset detection parsing crash in some cases when + the XML directive is not syntactic correct.</p> + <p> + Own Id: OTP-15492 Aux Id: ERIERL-283 </p> + </item> + </list> + </section> + +</section> + <section><title>Xmerl 1.3.16</title> <section><title>Fixed Bugs and Malfunctions</title> @@ -1412,4 +1442,3 @@ </section> </section> </chapter> - diff --git a/lib/xmerl/doc/src/xmerl_sax_parser.xml b/lib/xmerl/doc/src/xmerl_sax_parser.xml index 8ea197e209..2390779028 100644 --- a/lib/xmerl/doc/src/xmerl_sax_parser.xml +++ b/lib/xmerl/doc/src/xmerl_sax_parser.xml @@ -31,7 +31,7 @@ <rev></rev> </header> - <module>xmerl_sax_parser</module> + <module since="">xmerl_sax_parser</module> <modulesummary>XML SAX parser API</modulesummary> <description> @@ -325,7 +325,7 @@ <funcs> <func> - <name>file(Filename, Options) -> Result</name> + <name since="">file(Filename, Options) -> Result</name> <fsummary>Parse file containing an XML document.</fsummary> <type> <v>Filename = string()</v> @@ -347,7 +347,7 @@ </func> <func> - <name>stream(Xml, Options) -> Result</name> + <name since="">stream(Xml, Options) -> Result</name> <fsummary>Parse a stream containing an XML document.</fsummary> <type> <v>Xml = unicode_binary() | latin1_binary() | [unicode_char()]</v> @@ -381,7 +381,7 @@ <funcs> <func> - <name>ContinuationFun(State) -> {NewBytes, NewState}</name> + <name since="">ContinuationFun(State) -> {NewBytes, NewState}</name> <fsummary>Continuation call back function.</fsummary> <type> <v>State = NewState = term()</v> @@ -402,7 +402,7 @@ </func> <func> - <name>EventFun(Event, Location, State) -> NewState</name> + <name since="">EventFun(Event, Location, State) -> NewState</name> <fsummary>Event call back function.</fsummary> <type> <v>Event = event()</v> diff --git a/lib/xmerl/src/xmerl_sax_parser.erl b/lib/xmerl/src/xmerl_sax_parser.erl index e383c4c349..fe836fd8cd 100644 --- a/lib/xmerl/src/xmerl_sax_parser.erl +++ b/lib/xmerl/src/xmerl_sax_parser.erl @@ -1,8 +1,8 @@ %%-------------------------------------------------------------------- %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2008-2017. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. +%% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at @@ -14,13 +14,13 @@ %% 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% %%---------------------------------------------------------------------- %% File : xmerl_sax_parser.erl %% Description : XML SAX parse API module. %% -%% Created : 4 Jun 2008 +%% Created : 4 Jun 2008 %%---------------------------------------------------------------------- -module(xmerl_sax_parser). @@ -72,9 +72,9 @@ file(Name,Options) -> CL = filename:absname(Dir), File = filename:basename(Name), ContinuationFun = fun default_continuation_cb/1, - Res = stream(<<>>, + Res = stream(<<>>, [{continuation_fun, ContinuationFun}, - {continuation_state, FD}, + {continuation_state, FD}, {current_location, CL}, {entity, File} |Options], @@ -101,39 +101,39 @@ stream(Xml, Options, InputType) when is_list(Xml), is_list(Options) -> State = parse_options(Options, initial_state()), case State#xmerl_sax_parser_state.file_type of dtd -> - xmerl_sax_parser_list:parse_dtd(Xml, + xmerl_sax_parser_list:parse_dtd(Xml, State#xmerl_sax_parser_state{encoding = list, input_type = InputType}); normal -> - xmerl_sax_parser_list:parse(Xml, + xmerl_sax_parser_list:parse(Xml, State#xmerl_sax_parser_state{encoding = list, input_type = InputType}) end; stream(Xml, Options, InputType) when is_binary(Xml), is_list(Options) -> - case parse_options(Options, initial_state()) of + case parse_options(Options, initial_state()) of {error, Reason} -> {error, Reason}; State -> - ParseFunction = + ParseFunction = case State#xmerl_sax_parser_state.file_type of dtd -> parse_dtd; normal -> parse end, - try + try {Xml1, State1} = detect_charset(Xml, State), parse_binary(Xml1, State1#xmerl_sax_parser_state{input_type = InputType}, ParseFunction) catch throw:{fatal_error, {State2, Reason}} -> - {fatal_error, + {fatal_error, { State2#xmerl_sax_parser_state.current_location, - State2#xmerl_sax_parser_state.entity, + State2#xmerl_sax_parser_state.entity, 1 }, - Reason, [], + Reason, [], State2#xmerl_sax_parser_state.event_state} end end. @@ -157,7 +157,7 @@ parse_binary(Xml, #xmerl_sax_parser_state{encoding={utf16,big}}=State, F) -> xmerl_sax_parser_utf16be:F(Xml, State); parse_binary(Xml, #xmerl_sax_parser_state{encoding=latin1}=State, F) -> xmerl_sax_parser_latin1:F(Xml, State); -parse_binary(_, #xmerl_sax_parser_state{encoding=Enc}, State) -> +parse_binary(_, #xmerl_sax_parser_state{encoding=Enc}, State) -> ?fatal_error(State, lists:flatten(io_lib:format("Charcter set ~p not supported", [Enc]))). %%---------------------------------------------------------------------- @@ -177,9 +177,9 @@ initial_state() -> %%---------------------------------------------------------------------- %% Function: parse_options(Options, State) %% Input: Options = [Option] -%% Option = {event_state, term()} | {event_fun, fun()} | +%% Option = {event_state, term()} | {event_fun, fun()} | %% {continuation_state, term()} | {continuation_fun, fun()} | -%% {encoding, Encoding} | {file_type, FT} +%% {encoding, Encoding} | {file_type, FT} %% FT = normal | dtd %% Encoding = utf8 | utf16le | utf16be | list | iso8859 %% State = #xmerl_sax_parser_state{} @@ -200,7 +200,7 @@ parse_options([{file_type, FT} |Options], State) when FT==normal; FT==dtd -> parse_options(Options, State#xmerl_sax_parser_state{file_type = FT}); parse_options([{encoding, E} |Options], State) -> case check_encoding_option(E) of - {error, Reason} -> + {error, Reason} -> {error, Reason}; Enc -> parse_options(Options, State#xmerl_sax_parser_state{encoding = Enc}) @@ -231,7 +231,7 @@ check_encoding_option(E) -> %% Description: Detects which character set is used in a binary stream. %%---------------------------------------------------------------------- detect_charset(<<>>, #xmerl_sax_parser_state{continuation_fun = undefined} = State) -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); detect_charset(<<>>, State) -> cf(<<>>, State, fun detect_charset/2); detect_charset(Bytes, State) -> @@ -269,22 +269,14 @@ detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D>> = Xml, State) -> cf(Xml, State, fun detect_charset_1/2); detect_charset_1(<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml2/binary>>, State) -> {Xml3, State1} = read_until_end_of_xml_directive(Xml2, State), - case parse_xml_directive(Xml3) of - {error, Reason} -> - ?fatal_error(State, Reason); - AttrList -> - case lists:keysearch("encoding", 1, AttrList) of - {value, {_, E}} -> - case convert_encoding(E) of - {error, Reason} -> - ?fatal_error(State, Reason); - Enc -> - {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, - State1#xmerl_sax_parser_state{encoding=Enc}} - end; - _ -> - {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, State1} - end + AttrList = parse_xml_directive(Xml3, State), + case lists:keysearch("encoding", 1, AttrList) of + {value, {_, E}} -> + Enc = convert_encoding(E, State), + {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, + State1#xmerl_sax_parser_state{encoding=Enc}}; + _ -> + {<<16#3C, 16#3F, 16#78, 16#6D, 16#6C, Xml3/binary>>, State1} end; detect_charset_1(Xml, State) -> {Xml, State}. @@ -295,7 +287,7 @@ detect_charset_1(Xml, State) -> %% Output: utf8 | iso8859 %% Description: Converting 7,8 bit and utf8 encoding strings to internal format. %%---------------------------------------------------------------------- -convert_encoding(Enc) -> %% Just for 7,8 bit + utf8 +convert_encoding(Enc, State) -> %% Just for 7,8 bit + utf8 case string:to_lower(Enc) of "utf-8" -> utf8; "us-ascii" -> utf8; @@ -309,19 +301,19 @@ convert_encoding(Enc) -> %% Just for 7,8 bit + utf8 "iso-8859-7" -> latin1; "iso-8859-8" -> latin1; "iso-8859-9" -> latin1; - _ -> {error, "Unknown encoding: " ++ Enc} + _ -> ?fatal_error(State, "Unknown encoding: " ++ Enc) end. %%---------------------------------------------------------------------- %% Function: parse_xml_directive(Xml) %% Input: Xml = binary() %% Acc = list() -%% Output: +%% Output: %% Description: Parsing the xml declaration from the input stream. %%---------------------------------------------------------------------- -parse_xml_directive(<<C, Rest/binary>>) when ?is_whitespace(C) -> - parse_xml_directive_1(Rest, []). - +parse_xml_directive(<<C, Rest/binary>>, State) when ?is_whitespace(C) -> + parse_xml_directive_1(Rest, [], State). + %%---------------------------------------------------------------------- %% Function: parse_xml_directive_1(Xml, Acc) -> [{Name, Value}] %% Input: Xml = binary() @@ -331,20 +323,20 @@ parse_xml_directive(<<C, Rest/binary>>) when ?is_whitespace(C) -> %% Output: see above %% Description: Parsing the xml declaration from the input stream. %%---------------------------------------------------------------------- -parse_xml_directive_1(<<C, Rest/binary>>, Acc) when ?is_whitespace(C) -> - parse_xml_directive_1(Rest, Acc); -parse_xml_directive_1(<<"?>", _/binary>>, Acc) -> +parse_xml_directive_1(<<C, Rest/binary>>, Acc, State) when ?is_whitespace(C) -> + parse_xml_directive_1(Rest, Acc, State); +parse_xml_directive_1(<<"?>", _/binary>>, Acc, _State) -> Acc; -parse_xml_directive_1(<<C, Rest/binary>>, Acc) when 97 =< C, C =< 122 -> +parse_xml_directive_1(<<C, Rest/binary>>, Acc, State) when 97 =< C, C =< 122 -> {Name, Rest1} = parse_name(Rest, [C]), - Rest2 = parse_eq(Rest1), - {Value, Rest3} = parse_value(Rest2), - parse_xml_directive_1(Rest3, [{Name, Value} |Acc]); -parse_xml_directive_1(_, _) -> - {error, "Unknown attribute in xml directive"}. + Rest2 = parse_eq(Rest1, State), + {Value, Rest3} = parse_value(Rest2, State), + parse_xml_directive_1(Rest3, [{Name, Value} |Acc], State); +parse_xml_directive_1(_, _, State) -> + ?fatal_error(State, "Unknown attribute in xml directive"). %%---------------------------------------------------------------------- -%% Function: parse_xml_directive_1(Xml, Acc) -> Name +%% Function: parse_name(Xml, Acc) -> Name %% Input: Xml = binary() %% Acc = string() %% Output: Name = string() @@ -361,10 +353,12 @@ parse_name(Rest, Acc) -> %% Output: Rest = binary() %% Description: Reads an '=' from the stream. %%---------------------------------------------------------------------- -parse_eq(<<C, Rest/binary>>) when ?is_whitespace(C) -> - parse_eq(Rest); -parse_eq(<<"=", Rest/binary>>) -> - Rest. +parse_eq(<<C, Rest/binary>>, State) when ?is_whitespace(C) -> + parse_eq(Rest, State); +parse_eq(<<"=", Rest/binary>>, _State) -> + Rest; +parse_eq(_, State) -> + ?fatal_error(State, "expecting = or whitespace"). %%---------------------------------------------------------------------- %% Function: parse_value(Xml) -> {Value, Rest} @@ -373,10 +367,12 @@ parse_eq(<<"=", Rest/binary>>) -> %% Rest = binary() %% Description: Parsing an attribute value from the stream. %%---------------------------------------------------------------------- -parse_value(<<C, Rest/binary>>) when ?is_whitespace(C) -> - parse_value(Rest); -parse_value(<<C, Rest/binary>>) when C == $'; C == $" -> - parse_value_1(Rest, C, []). +parse_value(<<C, Rest/binary>>, State) when ?is_whitespace(C) -> + parse_value(Rest, State); +parse_value(<<C, Rest/binary>>, _State) when C == $'; C == $" -> + parse_value_1(Rest, C, []); +parse_value(_, State) -> + ?fatal_error(State, "\', \" or whitespace expected"). %%---------------------------------------------------------------------- %% Function: parse_value_1(Xml, Stop, Acc) -> {Value, Rest} @@ -431,7 +427,7 @@ read_until_end_of_xml_directive(Rest, State) -> nomatch -> case cf(Rest, State) of {<<>>, _} -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); {NewBytes, NewState} -> read_until_end_of_xml_directive(NewBytes, NewState) end; @@ -450,9 +446,9 @@ read_until_end_of_xml_directive(Rest, State) -> %% input stream and calls the fun in NextCall. %%---------------------------------------------------------------------- cf(_Rest, #xmerl_sax_parser_state{continuation_fun = undefined} = State) -> - ?fatal_error(State, "Continuation function undefined"); + ?fatal_error(State, "Continuation function undefined"); cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State) -> - Result = + Result = try CFun(CState) catch @@ -463,9 +459,9 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C end, case Result of {<<>>, _} -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); {NewBytes, NewContState} -> - {<<Rest/binary, NewBytes/binary>>, + {<<Rest/binary, NewBytes/binary>>, State#xmerl_sax_parser_state{continuation_state = NewContState}} end. @@ -479,10 +475,10 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C %% input stream and calls the fun in NextCall. %%---------------------------------------------------------------------- cf(_Rest, #xmerl_sax_parser_state{continuation_fun = undefined} = State, _) -> - ?fatal_error(State, "Continuation function undefined"); -cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State, + ?fatal_error(State, "Continuation function undefined"); +cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = CState} = State, NextCall) -> - Result = + Result = try CFun(CState) catch @@ -493,8 +489,8 @@ cf(Rest, #xmerl_sax_parser_state{continuation_fun = CFun, continuation_state = C end, case Result of {<<>>, _} -> - ?fatal_error(State, "Can't detect character encoding due to lack of indata"); + ?fatal_error(State, "Can't detect character encoding due to lack of indata"); {NewBytes, NewContState} -> - NextCall(<<Rest/binary, NewBytes/binary>>, + NextCall(<<Rest/binary, NewBytes/binary>>, State#xmerl_sax_parser_state{continuation_state = NewContState}) end. diff --git a/lib/xmerl/test/xmerl_xsd_lib.erl b/lib/xmerl/test/xmerl_xsd_lib.erl index 6006cf500f..39300cd6cb 100644 --- a/lib/xmerl/test/xmerl_xsd_lib.erl +++ b/lib/xmerl/test/xmerl_xsd_lib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2016. All Rights Reserved. +%% Copyright Ericsson AB 2006-2019. 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. @@ -94,8 +94,9 @@ return_results2({NewFail, NewSuccess, NewMal, NewNotMal}, NumSucc, SkippedN, Tot _ -> io_lib:format("These ~p skipped tests were malicious, but succeeds now: ~p~n", [length(NewNotMal), NewNotMal]) end, - ct:comment(io_lib:format("~p successful tests, ~p skipped tests of totally ~p test cases. ~n" ++ - NFComm ++ NSComm ++ NMComm ++ NNMComm, [NumSucc, SkippedN, TotN])), + ct:comment(io_lib:format("~p successful tests, ~p skipped tests of totally ~p test cases. ~n~ts", + [NumSucc, SkippedN, TotN, + NFComm ++ NSComm ++ NMComm ++ NNMComm])), [] = NewFail. %% return_results2(Diff,{STErrs,STOther},{ITErrs,ITOther},TotN) -> diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk index 3a266a56bd..b6486681c2 100644 --- a/lib/xmerl/vsn.mk +++ b/lib/xmerl/vsn.mk @@ -1 +1 @@ -XMERL_VSN = 1.3.18 +XMERL_VSN = 1.3.19 |