diff options
37 files changed, 627 insertions, 263 deletions
diff --git a/erts/configure b/erts/configure index a84ffe4342..fbdb6baba8 100755 --- a/erts/configure +++ b/erts/configure @@ -13973,7 +13973,7 @@ int main (void) { - __asm__ __volatile__("isb sy" : : : "memory"); + __asm__ __volatile__("isb sy\n" : : : "memory"); ; return 0; @@ -14007,7 +14007,7 @@ int main (void) { - char data[512]; __asm__ __volatile__("dc cvau, %0" : "r" (data) : : "memory"); + char data[512]; __asm__ __volatile__("dc cvau, %0\n" :: "r" (data) : "memory"); ; return 0; @@ -14041,7 +14041,7 @@ int main (void) { - char data[512]; __asm__ __volatile__("ic ivau, %0" : "r" (data) : : "memory"); + char data[512]; __asm__ __volatile__("ic ivau, %0\n" :: "r" (data) : "memory"); ; return 0; @@ -24313,7 +24313,27 @@ then : JIT_ARCH=x86 ;; arm64) - JIT_ARCH=arm + case "$OPSYS" in + win32|darwin) + # These platforms have dedicated system calls for clearing + # instruction cache, and don't require us to manually issue + # instruction barriers on all threads. + JIT_ARCH=arm + ;; + *) + # We need to use `DC CVAU`, `IC IVAU`, and `ISB SY` to clear + # instruction cache. These have already been tested as part of + # ETHR_CHK_GCC_ATOMIC_OPS([]). + + if test "$ethr_arm_isb_sy_instr_val$ethr_arm_dc_cvau_instr_val$ethr_arm_ic_ivau_instr_val" = "111"; then + JIT_ARCH=arm + else + enable_jit=no + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: WARNING: JIT disabled due to lack of cache-clearing instructions" >&5 +printf "%s\n" "$as_me: WARNING: JIT disabled due to lack of cache-clearing instructions" >&2;} + fi + ;; + esac ;; *) if test ${enable_jit} = yes; then diff --git a/erts/configure.ac b/erts/configure.ac index ab2ee78acd..a052c319ba 100644 --- a/erts/configure.ac +++ b/erts/configure.ac @@ -2855,7 +2855,26 @@ AS_IF([test ${enable_jit} != no], JIT_ARCH=x86 ;; arm64) - JIT_ARCH=arm + case "$OPSYS" in + win32|darwin) + # These platforms have dedicated system calls for clearing + # instruction cache, and don't require us to manually issue + # instruction barriers on all threads. + JIT_ARCH=arm + ;; + *) + # We need to use `DC CVAU`, `IC IVAU`, and `ISB SY` to clear + # instruction cache. These have already been tested as part of + # ETHR_CHK_GCC_ATOMIC_OPS([]). + + if test "$ethr_arm_isb_sy_instr_val$ethr_arm_dc_cvau_instr_val$ethr_arm_ic_ivau_instr_val" = "111"; then + JIT_ARCH=arm + else + enable_jit=no + AC_MSG_WARN([JIT disabled due to lack of cache-clearing instructions]) + fi + ;; + esac ;; *) if test ${enable_jit} = yes; then diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml index 7f74df820d..f39f020c16 100644 --- a/erts/doc/src/notes.xml +++ b/erts/doc/src/notes.xml @@ -1392,6 +1392,22 @@ </section> +<section><title>Erts 12.3.2.10</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Active process aliases of a process at its termination + leaked memory.</p> + <p> + Own Id: OTP-18496 Aux Id: GH-6947, PR-6953 </p> + </item> + </list> + </section> + +</section> + <section><title>Erts 12.3.2.9</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/erts/emulator/beam/code_ix.c b/erts/emulator/beam/code_ix.c index 607f3c82c6..8f393f92cf 100644 --- a/erts/emulator/beam/code_ix.c +++ b/erts/emulator/beam/code_ix.c @@ -347,7 +347,17 @@ static ErtsThrPrgrLaterOp global_code_barrier_lop; static void decrement_blocking_code_barriers(void *ignored) { (void)ignored; - erts_atomic32_dec_nob(&outstanding_blocking_code_barriers); + + if (erts_atomic32_dec_read_nob(&outstanding_blocking_code_barriers) > 0) { + /* We had more than one barrier in the same tick, and can't tell + * whether the later ones were issued before any of the managed threads + * were woken. Keep telling all managed threads to execute an + * instruction barrier on wake-up for one more tick. */ + erts_atomic32_set_nob(&outstanding_blocking_code_barriers, 1); + erts_schedule_thr_prgr_later_op(decrement_blocking_code_barriers, + NULL, + &global_code_barrier_lop); + } } static void schedule_blocking_code_barriers(void *ignored) { @@ -358,11 +368,12 @@ static void schedule_blocking_code_barriers(void *ignored) { * counter. * * Note that we increment and decrement instead of setting and clearing - * since we might execute several blocking barriers in the same tick. */ - erts_atomic32_inc_nob(&outstanding_blocking_code_barriers); - erts_schedule_thr_prgr_later_op(decrement_blocking_code_barriers, - NULL, - &global_code_barrier_lop); + * since we might schedule several blocking barriers in the same tick. */ + if (erts_atomic32_inc_read_nob(&outstanding_blocking_code_barriers) == 1) { + erts_schedule_thr_prgr_later_op(decrement_blocking_code_barriers, + NULL, + &global_code_barrier_lop); + } } #endif diff --git a/erts/emulator/beam/io.c b/erts/emulator/beam/io.c index dfdbe475b3..2ef5bc4d01 100644 --- a/erts/emulator/beam/io.c +++ b/erts/emulator/beam/io.c @@ -4659,6 +4659,7 @@ erts_port_call(Process* c_p, unsigned ret_flags = 0U; Eterm term; Eterm* hp; + ErtsPortOpResult result; res = call_driver_call(c_p->common.id, prt, @@ -4672,25 +4673,36 @@ erts_port_call(Process* c_p, finalize_imm_drv_call(&try_call_state); if (bufp != &input_buf[0]) erts_free(ERTS_ALC_T_TMP, bufp); - if (res == ERTS_PORT_OP_BADARG) - return ERTS_PORT_OP_BADARG; - hsz = erts_decode_ext_size((byte *) resp_bufp, resp_size); - if (hsz < 0) - return ERTS_PORT_OP_BADARG; - hsz += 3; - erts_factory_proc_prealloc_init(&factory, c_p, hsz); - endp = (byte *) resp_bufp; - term = erts_decode_ext(&factory, (const byte**)&endp, 0); - if (term == THE_NON_VALUE) - return ERTS_PORT_OP_BADARG; - hp = erts_produce_heap(&factory,3,0); - *retvalp = TUPLE2(hp, am_ok, term); - erts_factory_close(&factory); + if (res == ERTS_PORT_OP_BADARG) { + result = ERTS_PORT_OP_BADARG; + } + else { + hsz = erts_decode_ext_size((byte *) resp_bufp, resp_size); + if (hsz < 0) { + result = ERTS_PORT_OP_BADARG; + } + else { + hsz += 3; + erts_factory_proc_prealloc_init(&factory, c_p, hsz); + endp = (byte *) resp_bufp; + term = erts_decode_ext(&factory, (const byte**)&endp, 0); + if (term == THE_NON_VALUE) { + result = ERTS_PORT_OP_BADARG; + } + else { + hp = erts_produce_heap(&factory,3,0); + *retvalp = TUPLE2(hp, am_ok, term); + result = ERTS_PORT_OP_DONE; + } + erts_factory_close(&factory); + } + } if (resp_bufp != &resp_buf[0] - && !(ret_flags & DRIVER_CALL_KEEP_BUFFER)) + && !(ret_flags & DRIVER_CALL_KEEP_BUFFER)) { driver_free(resp_bufp); + } BUMP_REDS(c_p, ERTS_PORT_REDS_CALL); - return ERTS_PORT_OP_DONE; + return result; } case ERTS_TRY_IMM_DRV_CALL_INVALID_PORT: if (bufp != &input_buf[0]) diff --git a/erts/emulator/beam/jit/arm/ops.tab b/erts/emulator/beam/jit/arm/ops.tab index cc0bfeccf0..9cfce18e58 100644 --- a/erts/emulator/beam/jit/arm/ops.tab +++ b/erts/emulator/beam/jit/arm/ops.tab @@ -666,18 +666,14 @@ i_perf_counter bif0 u$bif:erlang:self/0 Dst=d => self Dst bif0 u$bif:erlang:node/0 Dst=d => node Dst -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=xy Dst => is_nonempty_list Fail Src | get_hd Src Dst bif1 Fail=p Bif=u$bif:erlang:hd/1 Src Dst => bif_hd Src Dst bif_hd s d -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=xy Dst => is_nonempty_list Fail Src | get_tl Src Dst bif1 Fail=p Bif=u$bif:erlang:tl/1 Src Dst => bif_tl Src Dst diff --git a/erts/emulator/beam/jit/beam_jit_main.cpp b/erts/emulator/beam/jit/beam_jit_main.cpp index 2ddde35511..ef0ad6943a 100644 --- a/erts/emulator/beam/jit/beam_jit_main.cpp +++ b/erts/emulator/beam/jit/beam_jit_main.cpp @@ -377,9 +377,9 @@ extern "C" /* Issues full memory/instruction barriers on all threads for us. */ sys_icache_invalidate((char *)address, size); #elif defined(__aarch64__) && defined(__GNUC__) && \ - defined(ETHR_HAVE_GCC_ASM_ARM_IC_IVAU_INSTRUCTION) && \ - defined(ETHR_HAVE_GCC_ASM_ARM_DC_CVAU_INSTRUCTION) && \ - defined(ERTS_THR_INSTRUCTION_BARRIER) + defined(ERTS_THR_INSTRUCTION_BARRIER) && \ + ETHR_HAVE_GCC_ASM_ARM_IC_IVAU_INSTRUCTION && \ + ETHR_HAVE_GCC_ASM_ARM_DC_CVAU_INSTRUCTION /* Note that we do not issue any barriers here, whether instruction or * memory. This is on purpose as we must issue those on all schedulers * and not just the calling thread, and the chances of us forgetting to diff --git a/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp b/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp index 4dfe39cb98..26133d5ac0 100644 --- a/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp +++ b/erts/emulator/beam/jit/x86/instr_guard_bifs.cpp @@ -60,24 +60,22 @@ void BeamGlobalAssembler::emit_handle_hd_error() { * The code size for this specialization of hd/1 is 21 bytes, * while the code size for the bif1 instruction is 24 bytes. */ -void BeamModuleAssembler::emit_bif_hd(const ArgLabel &Fail, - const ArgSource &Src, +void BeamModuleAssembler::emit_bif_hd(const ArgSource &Src, const ArgRegister &Hd) { + Label good_cons = a.newLabel(); + mov_arg(RET, Src); a.test(RETb, imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST)); - if (Fail.get() != 0) { - a.jne(resolve_beam_label(Fail)); - } else { - Label next = a.newLabel(); - a.short_().je(next); - safe_fragment_call(ga->get_handle_hd_error()); - a.bind(next); - } + a.short_().je(good_cons); + safe_fragment_call(ga->get_handle_hd_error()); - x86::Gp boxed_ptr = emit_ptr_val(RET, RET); - a.mov(ARG2, getCARRef(boxed_ptr)); - mov_arg(Hd, ARG2); + a.bind(good_cons); + { + x86::Gp boxed_ptr = emit_ptr_val(RET, RET); + a.mov(ARG2, getCARRef(boxed_ptr)); + mov_arg(Hd, ARG2); + } } void BeamGlobalAssembler::emit_handle_element_error() { diff --git a/erts/emulator/beam/jit/x86/ops.tab b/erts/emulator/beam/jit/x86/ops.tab index 1ce0d6f9c2..5481fff2d8 100644 --- a/erts/emulator/beam/jit/x86/ops.tab +++ b/erts/emulator/beam/jit/x86/ops.tab @@ -660,18 +660,14 @@ i_perf_counter bif0 u$bif:erlang:self/0 Dst=d => self Dst bif0 u$bif:erlang:node/0 Dst=d => node Dst -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:hd/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:hd/1 Src=xy Dst => is_nonempty_list Fail Src | get_hd Src Dst -bif1 Fail Bif=u$bif:erlang:hd/1 Src Dst => - bif_hd Fail Src Dst +bif1 Fail=p Bif=u$bif:erlang:hd/1 Src Dst => + bif_hd Src Dst -bif_hd j s d +bif_hd s d -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=n Dst => - jump Fail -bif1 Fail=f Bif=u$bif:erlang:tl/1 Src Dst => +bif1 Fail=f Bif=u$bif:erlang:tl/1 Src=xy Dst => is_nonempty_list Fail Src | get_tl Src Dst bif1 Fail Bif=u$bif:erlang:get/1 Src=s Dst=d => get(Src, Dst) diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 98eacce0fc..b38d288f99 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -2299,6 +2299,8 @@ update_type(Merge, With, #value_ref{}=Ref, Vst0) -> case Merge(Current, With) of none -> throw({type_conflict, Current, With}); + Current -> + Vst0; Type -> Vst = update_container_type(Type, Ref, Vst0), set_type(Type, Ref, Vst) diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl index d79753068f..d96f3a6ae0 100644 --- a/lib/compiler/src/sys_core_fold.erl +++ b/lib/compiler/src/sys_core_fold.erl @@ -215,15 +215,15 @@ expr(#c_tuple{anno=Anno,es=Es0}=Tuple, Ctxt, Sub) -> ann_c_tuple(Anno, Es) end; expr(#c_map{anno=Anno,arg=V0,es=Es0}=Map, Ctxt, Sub) -> - Es = pair_list(Es0, Ctxt, descend(Map, Sub)), + %% Warn for useless building, but always build the map + %% anyway to preserve a possible exception. case Ctxt of - effect -> - warn_useless_building(Map, Sub), - make_effect_seq(Es, Sub); - value -> - V = expr(V0, Ctxt, Sub), - ann_c_map(Anno,V,Es) - end; + effect -> warn_useless_building(Map, Sub); + value -> ok + end, + Es = pair_list(Es0, descend(Map, Sub)), + V = expr(V0, value, Sub), + ann_c_map(Anno, V, Es); expr(#c_binary{segments=Ss}=Bin0, Ctxt, Sub) -> %% Warn for useless building, but always build the binary %% anyway to preserve a possible exception. @@ -490,14 +490,12 @@ ifes_list(_FVar, [], _Safe) -> expr_list(Es, Ctxt, Sub) -> [expr(E, Ctxt, Sub) || E <- Es]. -pair_list(Es, Ctxt, Sub) -> - [pair(E, Ctxt, Sub) || E <- Es]. +pair_list(Es, Sub) -> + [pair(E, Sub) || E <- Es]. -pair(#c_map_pair{key=K,val=V}, effect, Sub) -> - make_effect_seq([K,V], Sub); -pair(#c_map_pair{key=K0,val=V0}=Pair, value=Ctxt, Sub) -> - K = expr(K0, Ctxt, Sub), - V = expr(V0, Ctxt, Sub), +pair(#c_map_pair{key=K0,val=V0}=Pair, Sub) -> + K = expr(K0, value, Sub), + V = expr(V0, value, Sub), Pair#c_map_pair{key=K,val=V}. bitstr_list(Es, Sub) -> diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 64a356c7e4..aba3b8dfe2 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -41,7 +41,8 @@ missing_return_type/1,will_succeed/1, bs_saved_position_units/1,parent_container/1, container_performance/1, - not_equal_inference/1]). + not_equal_inference/1, + inert_update_type/1]). -include_lib("common_test/include/ct.hrl"). @@ -77,7 +78,8 @@ groups() -> missing_return_type,will_succeed, bs_saved_position_units,parent_container, container_performance, - not_equal_inference]}]. + not_equal_inference, + inert_update_type]}]. init_per_suite(Config) -> test_lib:recompile(?MODULE), @@ -1045,5 +1047,18 @@ not_equal_inference(_Config) -> not_equal_inference_1(X) when (X /= []) /= is_port(0 div 0) -> [X || _ <- []]. +%% GH-6969: A type was made concrete even though that added no additional +%% information. +inert_update_type(_Config) -> + hello(<<"string">>, id(42)). + +hello(A, B) -> + mike([{sys_period, {A, B}}, {some_atom, B}]). + +mike([Head | _Rest]) -> joe(Head). + +joe({Name, 42}) -> Name; +joe({sys_period, {A, _B}}) -> {41, 42, A}. + id(I) -> I. diff --git a/lib/compiler/test/bif_SUITE.erl b/lib/compiler/test/bif_SUITE.erl index 8480569507..34bf54a871 100644 --- a/lib/compiler/test/bif_SUITE.erl +++ b/lib/compiler/test/bif_SUITE.erl @@ -24,7 +24,8 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, beam_validator/1,trunc_and_friends/1,cover_safe_and_pure_bifs/1, - cover_trim/1]). + cover_trim/1, + head_tail/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -37,7 +38,8 @@ groups() -> [beam_validator, trunc_and_friends, cover_safe_and_pure_bifs, - cover_trim + cover_trim, + head_tail ]}]. init_per_suite(Config) -> @@ -167,6 +169,28 @@ cover_trim_3(Header, N)-> false end. +%% GH-7024: The loader transformations for hd/1 and tl/1 were incorrect and +%% failed when certain optimizations were turned off. +head_tail(_Config) -> + {1, ok} = head_case(), + {1, ok} = tail_case(), + + 1 = hd(id([1])), + [] = tl(id([1])), + + ok. + +head_case() -> + case 1 of + X when hd(X) -> blurf; + X -> {X, ok} + end. + +tail_case() -> + case 1 of + X when tl(X) -> blurf; + X -> {X, ok} + end. id(I) -> I. diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl index 4da55bed2e..0bdcfab5b7 100644 --- a/lib/compiler/test/core_fold_SUITE.erl +++ b/lib/compiler/test/core_fold_SUITE.erl @@ -29,7 +29,7 @@ no_no_file/1,configuration/1,supplies/1, redundant_stack_frame/1,export_from_case/1, empty_values/1,cover_letrec_effect/1, - receive_effect/1]). + receive_effect/1,map_effect/1]). -export([foo/0,foo/1,foo/2,foo/3]). @@ -50,7 +50,7 @@ groups() -> no_no_file,configuration,supplies, redundant_stack_frame,export_from_case, empty_values,cover_letrec_effect, - receive_effect]}]. + receive_effect,map_effect]}]. init_per_suite(Config) -> @@ -700,4 +700,20 @@ receive_effect(_Config) -> do_receive_effect() -> {} = receive _ -> {} = {} end. +map_effect(_Config) -> + {'EXIT',{{badkey,key},_}} = catch map_effect_1(), + + {'EXIT',{{badkey,key},_}} = catch map_effect_2(#{}), + {'EXIT',{{badmap,no_map},_}} = catch map_effect_2(no_map), + + ok. + +map_effect_1() -> + #{}#{key := value}, + ok. + +map_effect_2(Map) -> + Map#{key := value}, + ok. + id(I) -> I. diff --git a/lib/eldap/doc/src/eldap.xml b/lib/eldap/doc/src/eldap.xml index 4d9ec96a70..aae0f346b9 100644 --- a/lib/eldap/doc/src/eldap.xml +++ b/lib/eldap/doc/src/eldap.xml @@ -548,6 +548,18 @@ Control2 = eldap:paged_result_control(PageSize, Cookie1), the series.</p> </desc> </func> + <func> + <name since="@OTP-18480@">info(Handle) -> connection_info()</name> + <fsummary>Returns information about the LDAP connection. + </fsummary> + <type> + <v>connection_info() = #{socket := Socket, socket_type := tcp | ssl}</v> + <v>Socket = ssl:sslsocket() | gen_tcp:socket()</v> + </type> + <desc><p>Currently available information reveals the socket and the transport + protocol, TCP or TLS (SSL), used by the LDAP connection.</p> + </desc> + </func> </funcs> diff --git a/lib/eldap/src/eldap.erl b/lib/eldap/src/eldap.erl index 22d816c8c8..7edd012263 100644 --- a/lib/eldap/src/eldap.erl +++ b/lib/eldap/src/eldap.erl @@ -30,7 +30,8 @@ parse_ldap_url/1, paged_result_control/1, paged_result_control/2, - paged_result_cookie/1]). + paged_result_cookie/1, + info/1]). -export([neverDerefAliases/0, derefInSearching/0, derefFindingBaseObj/0, derefAlways/0]). @@ -155,6 +156,13 @@ controlling_process(Handle, Pid) when is_pid(Handle), is_pid(Pid) -> recv(Handle). %%% -------------------------------------------------------------------- +%%% Return LDAP socket information +%%% -------------------------------------------------------------------- +info(Handle) when is_pid(Handle) -> + send(Handle, info), + recv(Handle). + +%%% -------------------------------------------------------------------- %%% Authenticate ourselves to the Directory %%% using simple authentication. %%% @@ -608,6 +616,18 @@ loop(Cpid, Data) -> send(From, Result), ?MODULE:loop(Cpid, Data); + {From, info} -> + SocketType = + case Data#eldap.ldaps of + true -> + ssl; + false -> + tcp + end, + Res = #{socket => Data#eldap.fd, socket_type => SocketType}, + send(From, Res), + ?MODULE:loop(Cpid, Data); + {Cpid, 'EXIT', Reason} -> ?PRINT("Got EXIT from Cpid, reason=~p~n",[Reason]), exit(Reason); @@ -618,7 +638,6 @@ loop(Cpid, Data) -> end. - %%% -------------------------------------------------------------------- %%% startTLS Request %%% -------------------------------------------------------------------- diff --git a/lib/eldap/test/eldap_basic_SUITE.erl b/lib/eldap/test/eldap_basic_SUITE.erl index 5fa6d4ca69..2b26ed52bd 100644 --- a/lib/eldap/test/eldap_basic_SUITE.erl +++ b/lib/eldap/test/eldap_basic_SUITE.erl @@ -46,6 +46,7 @@ more_add/1, open_ret_val_error/1, open_ret_val_success/1, + plain_ldap_socket_info/1, search_filter_and/1, search_filter_and_not/1, search_filter_equalityMatch/1, @@ -63,6 +64,7 @@ search_extensible_match_without_dn/1, search_paged_results/1, ssl_connection/1, + ssl_conn_socket_info/1, start_tls_on_ssl_should_fail/1, start_tls_twice_should_fail/1, tcp_connection/1, @@ -81,8 +83,8 @@ suite/0 ]). -%%-include_lib("common_test/include/ct.hrl"). -include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). -include_lib("eldap/include/eldap.hrl"). -include_lib("eldap/ebin/ELDAPv3.hrl"). @@ -159,7 +161,9 @@ connection_tests() -> client_side_bind_timeout, client_side_add_timeout, client_side_search_timeout, - close_after_tcp_error + close_after_tcp_error, + ssl_conn_socket_info, + plain_ldap_socket_info ]. @@ -259,7 +263,7 @@ end_per_group(start_tls_api, Config) -> clear_db(Config); end_per_group(_Group, Config) -> Config. -init_per_testcase(ssl_connection, Config) -> +init_per_testcase(TC, Config) when TC == ssl_connection; TC == ssl_conn_socket_info -> case proplists:get_value(ssl_available,Config) of true -> SSL_Port = 9999, @@ -423,6 +427,35 @@ ssl_connection(Config) -> end. %%%---------------------------------------------------------------- +ssl_conn_socket_info(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(ssl_listen_port, Config), + Opts = proplists:get_value(tcp_connect_opts, Config), + SSLOpts = proplists:get_value(ssl_connect_opts, Config), + case eldap:open([Host], [{port,Port}, + {ssl,true}, + {timeout,5000}, + {sslopts,SSLOpts}|Opts]) of + {ok,H} -> + #{socket := Socket, socket_type := ssl} = eldap:info(H), + ?assertMatch({ok, _Data}, ssl:connection_information(Socket)); + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + +%%%---------------------------------------------------------------- +plain_ldap_socket_info(Config) -> + Host = proplists:get_value(listen_host, Config), + Port = proplists:get_value(listen_port, Config), + Opts = proplists:get_value(tcp_connect_opts, Config), + T = 1000, + case eldap:open([Host], [{timeout,T},{port,Port}|Opts]) of + {ok,H} -> + ?assertMatch(#{socket := _, socket_type := tcp}, + eldap:info(H)); + Other -> ct:fail("eldap:open failed: ~p",[Other]) + end. + +%%%---------------------------------------------------------------- client_side_add_timeout(Config) -> client_timeout( fun(H) -> diff --git a/lib/erl_interface/configure b/lib/erl_interface/configure index acfd569e30..049c3371fc 100755 --- a/lib/erl_interface/configure +++ b/lib/erl_interface/configure @@ -8718,7 +8718,7 @@ int main (void) { - __asm__ __volatile__("isb sy" : : : "memory"); + __asm__ __volatile__("isb sy\n" : : : "memory"); ; return 0; @@ -8752,7 +8752,7 @@ int main (void) { - char data[512]; __asm__ __volatile__("dc cvau, %0" : "r" (data) : : "memory"); + char data[512]; __asm__ __volatile__("dc cvau, %0\n" :: "r" (data) : "memory"); ; return 0; @@ -8786,7 +8786,7 @@ int main (void) { - char data[512]; __asm__ __volatile__("ic ivau, %0" : "r" (data) : : "memory"); + char data[512]; __asm__ __volatile__("ic ivau, %0\n" :: "r" (data) : "memory"); ; return 0; diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index d935ad2f11..a6d3e10620 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -276,6 +276,23 @@ </section> + <section><title>Inets 7.5.3.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct timing related pipelining/keepalive queue bug, + that could result in unexpected "socket_remotly_closed" + errors.</p> + <p> + Own Id: OTP-18509 Aux Id: OTP-18476 </p> + </item> + </list> + </section> + +</section> + <section><title>Inets 7.5.3.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml index e692ca99bb..cb4eb5985f 100644 --- a/lib/snmp/doc/src/notes.xml +++ b/lib/snmp/doc/src/notes.xml @@ -163,6 +163,22 @@ </section> + <section><title>SNMP 5.12.0.3</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Attempts to minimize the number of the error reports + during a failed agent init.</p> + <p> + Own Id: OTP-18422 Aux Id: ERIERL-873 </p> + </item> + </list> + </section> + +</section> + <section><title>SNMP 5.12.0.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/snmp/doc/src/snmp_app.xml b/lib/snmp/doc/src/snmp_app.xml index 0613de3158..1400e7bf32 100644 --- a/lib/snmp/doc/src/snmp_app.xml +++ b/lib/snmp/doc/src/snmp_app.xml @@ -4,7 +4,7 @@ <appref> <header> <copyright> - <year>1997</year><year>2021</year> + <year>1997</year><year>2023</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -295,14 +295,15 @@ in the snmp_config file! <c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag> <item> <p><c>agent_net_if_option() = - {bind_to, bind_to()} | - {sndbuf, sndbuf()} | - {recbuf, recbuf()} | - {no_reuse, no_reuse()} | - {req_limit, req_limit()} | - {filter, agent_net_if_filter_options()} | - {extra_sock_opts, extra_socket_options()} | - {inet_backend, inet | socket}</c></p> + {bind_to, bind_to()} | + {sndbuf, sndbuf()} | + {recbuf, recbuf()} | + {no_reuse, no_reuse()} | + {req_limit, req_limit()} | + {filter, agent_net_if_filter_options()} | + {open_err_filters, agent_net_if_open_err_filters()} | + {extra_sock_opts, extra_socket_options()} | + {inet_backend, inet | socket}</c></p> <p>These options are actually specific to the used module. The ones shown here are applicable to the default <c>agent_net_if_module()</c>.</p> @@ -343,6 +344,25 @@ in the snmp_config file! <p>Default is <c>snmpa_net_if_filter</c>.</p> </item> + <tag><marker id="agent_ni_open_err_filters"></marker> + <c><![CDATA[agent_net_if_open_err_filters() = [agent_net_if_open_err_filter()] <optional>]]></c></tag> + <item> + <p><c>agent_net_if_open_err_filter() = atom()</c></p> + <p>During agent initiation, the transports UDP sockets are opened. + If this operation fails, the net-if (and the agent) fails to start + (crash). This (filter) list contains error (reasons) that will + make net-if fail "nicely". + This (filter) list, is supposed to contain errors that can be + returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open/1,2</seemfa>. + The effect is that any error returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open</seemfa> + which *are* in this list, will be considered + "non-fatal" and will only result in an info message, rather than + an error message. Net If, and the agent, will still crash, + but will produce a less obnoxious message. </p> + </item> + <tag><marker id="agent_mibs"></marker> <c><![CDATA[agent_mibs() = [string()] <optional>]]></c></tag> <item> diff --git a/lib/snmp/doc/src/snmp_config.xml b/lib/snmp/doc/src/snmp_config.xml index 0b8d786a2d..99324ed400 100644 --- a/lib/snmp/doc/src/snmp_config.xml +++ b/lib/snmp/doc/src/snmp_config.xml @@ -4,7 +4,7 @@ <chapter> <header> <copyright> - <year>1997</year><year>2021</year> + <year>1997</year><year>2023</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -281,14 +281,15 @@ <c><![CDATA[agent_net_if_options() = [agent_net_if_option()] <optional>]]></c></tag> <item> <p><c>agent_net_if_option() = - {bind_to, bind_to()} | - {sndbuf, sndbuf()} | - {recbuf, recbuf()} | - {no_reuse, no_reuse()} | - {req_limit, req_limit()} | - {filter, agent_net_if_filter_options()} | - {extra_sock_opts, extra_socket_options()} | - {inet_backend, inet | socket}</c></p> + {bind_to, bind_to()} | + {sndbuf, sndbuf()} | + {recbuf, recbuf()} | + {no_reuse, no_reuse()} | + {req_limit, req_limit()} | + {filter, agent_net_if_filter_options()} | + {extra_sock_opts, extra_socket_options()} | + {open_err_filters, agent_net_if_open_err_filters()} | + {inet_backend, inet | socket}</c></p> <p>These options are actually specific to the used module. The ones shown here are applicable to the default <c>agent_net_if_module()</c>.</p> @@ -329,6 +330,25 @@ <p>Default is <c>snmpa_net_if_filter</c>.</p> </item> + <tag><marker id="agent_ni_open_err_filters"></marker> + <c><![CDATA[agent_net_if_open_err_filters() = [agent_net_if_open_err_filter()] <optional>]]></c></tag> + <item> + <p><c>agent_net_if_open_err_filter() = atom()</c></p> + <p>During agent initiation, the transports UDP sockets are opened. + If this operation fails, the net-if (and the agent) fails to start + (crash). This (filter) list contains error (reasons) that will + make net-if fail "nicely". + This (filter) list, is supposed to contain errors that can be + returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open/1,2</seemfa>. + The effect is that any error returned by + <seemfa marker="kernel:gen_udp#open/1">gen_udp:open</seemfa> + which *are* in this list, will be considered + "non-fatal" and will only result in an info message, rather than + an error message. Net If, and the agent, will still crash, + but will produce a less obnoxious message. </p> + </item> + <tag><marker id="agent_mibs"></marker> <c><![CDATA[agent_mibs() = [string()] <optional>]]></c></tag> <item> diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl index 736b2907fa..8c24440efc 100644 --- a/lib/snmp/src/agent/snmpa_agent.erl +++ b/lib/snmp/src/agent/snmpa_agent.erl @@ -340,10 +340,16 @@ init([Prio, Parent, Ref, Options]) -> "~n Options: ~p", [Prio, Parent, Ref, Options]), case (catch do_init(Prio, Parent, Ref, Options)) of {ok, State} -> - ?vdebug("started",[]), + ?vdebug("started"), {ok, State}; + {error, {net_if, info, Reason}} -> + info_msg("Failed starting agent: " + "~n Net If error: ~p", [Reason]), + %% {shutdown, Reason}; + exit(Reason); {error, Reason} -> - config_err("failed starting agent: ~n~p", [Reason]), + config_err("Failed starting agent: " + "~n ~p", [Reason]), {stop, Reason} end. @@ -410,15 +416,18 @@ start_note_store(Prio, Ref, Options) -> {ok, Pid} -> ?vdebug("start_note_store -> Pid: ~p", [Pid]), Pid; - {error, Reason} -> - ?vinfo("error starting note store: ~n~p",[Reason]), - throw({error, {note_store_error, Reason}}); - {'EXIT', Reason} -> - ?vinfo("exit starting note store: ~n~p",[Reason]), - throw({error, {note_store_exit, Reason}}); + {error, {Reason, _ChildSpec}} -> + ?vinfo("error starting note store: " + "~n ~p", [Reason]), + throw({error, {note_store, error, Reason}}); + {'EXIT', {Reason, _ChildSpec}} -> + ?vinfo("exit starting note store: " + "~n ~p", [Reason]), + throw({error, {note_store, exit, Reason}}); Error -> - ?vinfo("failed starting note store: ~n~p",[Error]), - throw({error, {note_store_failed, Error}}) + ?vinfo("failed starting note store: " + "~n ~p", [Error]), + throw({error, {note_store, failed, Error}}) end. @@ -437,18 +446,25 @@ start_net_if(none, Prio, Ref, Vsns, NoteStore, Options) -> case (catch snmpa_misc_sup:start_net_if(Prio, NoteStore, Ref, self(), Mod, NiOpts)) of - {ok, Pid} -> + {ok, Pid} -> ?vdebug("start_net_if -> Pid: ~p", [Pid]), {master_agent, Pid, Mod}; - {error, Reason} -> - ?vinfo("error starting net if: ~n~p",[Reason]), - throw({error, {net_if_error, Reason}}); + {error, {{Class, udp_open, PortNo, Reason}, _ChildSpec}} -> + ?vinfo("error starting net if: " + "~n ~p", [Reason]), + throw({error, {net_if, Class, {udp_open, PortNo, Reason}}}); + {error, {Reason, _ChildSpec}} -> + ?vinfo("error starting net if: " + "~n ~p", [Reason]), + throw({error, {net_if, error, Reason}}); {'EXIT', Reason} -> - ?vinfo("exit starting net if: ~n~p",[Reason]), - throw({error, {net_if_exit, Reason}}); + ?vinfo("exit starting net if: " + "~n ~p", [Reason]), + throw({error, {net_if, exit, Reason}}); Error -> - ?vinfo("failed starting net if: ~n~p",[Error]), - throw({error, {net_if_failed, Error}}) + ?vinfo("failed starting net if: " + "~n ~p", [Error]), + throw({error, {net_if, failed, Error}}) end; start_net_if(Parent, _Prio, _Ref, _Vsns, _NoteStore, _Options) when is_pid(Parent) -> @@ -470,15 +486,18 @@ start_mib_server(Prio, Ref, Mibs, Options) -> {ok, Pid} -> ?vdebug("start_mib_server -> Pid: ~p", [Pid]), Pid; - {error, Reason} -> - ?vinfo("error starting mib server: ~n~p",[Reason]), - throw({error, {mib_server_error, Reason}}); + {error, {Reason, _ChildSpec}} -> + ?vinfo("error starting mib server: " + "~n ~p", [Reason]), + throw({error, {mib_server, error, Reason}}); {'EXIT', Reason} -> - ?vinfo("exit starting mib server: ~n~p",[Reason]), - throw({error, {mib_server_exit, Reason}}); + ?vinfo("exit starting mib server: " + "~n ~p", [Reason]), + throw({error, {mib_server, exit, Reason}}); Error -> - ?vinfo("failed starting mib server: ~n~p",[Error]), - throw({error, {mib_server_failed, Error}}) + ?vinfo("failed starting mib server: " + "~n ~p", [Error]), + throw({error, {mib_server, failed, Error}}) end. @@ -3204,8 +3223,8 @@ get_stats_counters([Counter|Counters], Acc) -> %% --------------------------------------------------------------------- -%% info_msg(F, A) -> -%% ?snmpa_info(F, A). +info_msg(F, A) -> + ?snmpa_info(F, A). warning_msg(F, A) -> ?snmpa_warning(F, A). diff --git a/lib/snmp/src/agent/snmpa_agent_sup.erl b/lib/snmp/src/agent/snmpa_agent_sup.erl index 0a5116b2d0..b1eeaba988 100644 --- a/lib/snmp/src/agent/snmpa_agent_sup.erl +++ b/lib/snmp/src/agent/snmpa_agent_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2016. All Rights Reserved. +%% Copyright Ericsson AB 1997-2023. 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. @@ -24,7 +24,9 @@ -behaviour(supervisor). %% External exports --export([start_link/0, start_link/1, start_subagent/3, stop_subagent/1]). +-export([start_link/0, start_link/1, + start_master_agent/1, + start_subagent/3, stop_subagent/1]). %% Internal exports -export([init/1]). @@ -40,8 +42,7 @@ %%%----------------------------------------------------------------- -%%% This is a supervisor for the mib processes. Each agent has one -%%% mib process. +%%% This is a supervisor for the agent processes (master and sub). %%%----------------------------------------------------------------- start_link() -> ?d("start_link -> entry", []), @@ -52,6 +53,9 @@ start_link(AgentSpec) -> "~n AgentSpec: ~p", [AgentSpec]), supervisor:start_link({local, ?SERVER}, ?MODULE, [[AgentSpec]]). +start_master_agent(MasterAgentSpec) -> + supervisor:start_child(snmpa_agent_sup, MasterAgentSpec). + start_subagent(ParentAgent, Subtree, Mibs) -> ?d("start_subagent -> entry with" "~n ParentAgent: ~p" diff --git a/lib/snmp/src/agent/snmpa_net_if.erl b/lib/snmp/src/agent/snmpa_net_if.erl index d37aa6c28a..72e3ac3e41 100644 --- a/lib/snmp/src/agent/snmpa_net_if.erl +++ b/lib/snmp/src/agent/snmpa_net_if.erl @@ -235,11 +235,21 @@ init(Prio, NoteStore, MasterAgent, Parent, Opts) -> end, erlang:raise(C, E, S) end; - {error, Reason} -> - config_err("failed starting net-if: ~n~p", [Reason]), - proc_lib:init_ack({error, Reason}); + {error, {udp_open, {open, PortNo, Reason}}} -> + OEFilters = get_open_err_filters(Opts), + Class = + case lists:member(Reason, OEFilters) of + false -> + error; + true -> + info + end, + proc_lib:init_ack({error, {Class, udp_open, PortNo, Reason}}); + {error, Reason} -> + %% config_err("failed starting net-if: ~n~p", [Reason]), + proc_lib:init_ack({error, Reason}); Error -> - config_err("failed starting net-if: ~n~p", [Error]), + %% config_err("failed starting net-if: ~n~p", [Error]), proc_lib:init_ack({error, Error}) end. @@ -256,7 +266,7 @@ do_init(Prio, NoteStore, MasterAgent, Parent, Opts) -> Vsns = get_vsns(Opts), ?vdebug("vsns: ~w",[Vsns]), - %% Flow control -- + %% -- Flow control -- Limit = get_req_limit(Opts), ?vdebug("Limit: ~w", [Limit]), FilterOpts = get_filter_opts(Opts), @@ -475,7 +485,7 @@ gen_udp_open(system, Opts) -> throw({udp_open, {port, PReason}}) end; {error, OReason} -> - throw({udp_open, {open, OReason}}) + throw({udp_open, {open, 0, OReason}}) end; %% This is for "future compat" since we cannot actually config '0'... gen_udp_open(IpPort, Opts) when (IpPort =:= 0) -> @@ -533,7 +543,7 @@ gen_udp_range_open(Min, Max, Opts) -> gen_udp_range_open(Min+1, Max, Opts); {error, Reason} -> ?vdebug("gen_udp_range_open(~w,~w) -> ~w", [Reason]), - throw({udp_open, {open, Reason}}) + throw({udp_open, {open, Min, Reason}}) catch C:E:S -> ?vinfo("gen_udp_range_open(~w,~w) -> failed open socket: " @@ -2101,6 +2111,16 @@ get_filter_opts(O) -> get_filter_module(O) -> snmp_misc:get_option(module, O, ?DEFAULT_FILTER_MODULE). +get_open_err_filters(O) -> + case snmp_misc:get_option(open_err_filters, O, []) of + Filters when is_list(Filters) -> + Filters; + Filter when is_atom(Filter) -> + [Filter]; + _ -> + [] + end. + get_recbuf(Opts, DefaultOpts) -> get_socket_opt(recbuf, Opts, DefaultOpts, use_default). @@ -2153,8 +2173,8 @@ info_msg(F,A) -> user_err(F, A) -> snmpa_error:user_err(F, A). -config_err(F, A) -> - snmpa_error:config_err(F, A). +%% config_err(F, A) -> +%% snmpa_error:config_err(F, A). %% ---------------------------------------------------------------- diff --git a/lib/snmp/src/agent/snmpa_supervisor.erl b/lib/snmp/src/agent/snmpa_supervisor.erl index 215851f23c..26c61579d5 100644 --- a/lib/snmp/src/agent/snmpa_supervisor.erl +++ b/lib/snmp/src/agent/snmpa_supervisor.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2022. All Rights Reserved. +%% Copyright Ericsson AB 1996-2023. 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. @@ -29,6 +29,7 @@ %% Internal exports -export([init/1, config/2]). +-compile({no_auto_import,[erase/1]}). -define(SERVER, ?MODULE). @@ -41,6 +42,10 @@ %% Process structure %% ================= %% +%% "application" +%% | +%% app-sup +%% | %% ___________________ supervisor __________________ %% / | | \ \ %% ___misc_sup___ target_cache symbolic_store local_db agent_sup @@ -176,7 +181,23 @@ start_master_sup(Opts) -> do_start_master_sup(Opts) -> verify_mandatory([db_dir], Opts), - supervisor:start_link({local, ?SERVER}, ?MODULE, [master, Opts]). + case supervisor:start_link({local, ?SERVER}, ?MODULE, [master, Opts]) of + {ok, Pid} = OK -> + %% <HACKETI-HACK-HACK> + Key = master_agent_child_spec, + MasterAgentSpec = lookup(Key), + case snmpa_agent_sup:start_master_agent(MasterAgentSpec) of + {ok, MPid} when is_pid(MPid) -> + erase(Key), + OK; + {error, {Reason, _ChildSpec}} -> + stop(Pid, 0), + {error, Reason} + end; + %% </HACKETI-HACK-HACK> + Else -> + Else + end. verify_mandatory([], _) -> ok; @@ -503,9 +524,18 @@ init([AgentType, Opts]) -> worker_spec(snmpa_agent, [Prio, snmp_master_agent, none, Ref, AgentOpts], Restart, 15000), + %% <HACKETI-HACK-HACK> + %% The point is to make start failure more quiet + %% Often the failure happens in the master agent, + %% so we move the start of that out of this function + %% and into the 'do_start_master_sup' function. + %% At some point we should rewrite this. Maybe start all + %% children the same way (explicitly). + store(master_agent_child_spec, AgentSpec), AgentSupSpec = - sup_spec(snmpa_agent_sup, [AgentSpec], + sup_spec(snmpa_agent_sup, [], Restart, infinity), + %% </HACKETI-HACK-HACK> [ConfigSpec, AgentSupSpec]; _ -> ?vdebug("[sub agent] spec for the agent supervisor",[]), @@ -521,6 +551,13 @@ init([AgentType, Opts]) -> store(Key, Value) -> ets:insert(snmp_agent_table, {Key, Value}). +lookup(Key) -> + [{Key, Value}] = ets:lookup(snmp_agent_table, Key), + Value. + +erase(Key) -> + ets:delete(snmp_agent_table, Key). + get_mibs(Mibs, Vsns) -> MibDir = filename:join(code:priv_dir(snmp), "mibs"), StdMib = diff --git a/lib/snmp/src/app/snmp_app.erl b/lib/snmp/src/app/snmp_app.erl index 486b276383..7ba3c659f0 100644 --- a/lib/snmp/src/app/snmp_app.erl +++ b/lib/snmp/src/app/snmp_app.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2021. All Rights Reserved. +%% Copyright Ericsson AB 2003-2023. 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. @@ -38,8 +38,13 @@ start(Type, []) -> %% First start the (new) central supervisor, {ok, Pid} = snmp_app_sup:start_link(), Entities = entities(), - ok = start_entities(Type, Entities), - {ok, Pid}. + case start_entities(Type, Entities) of + ok -> + {ok, Pid}; + Error -> + snmp_app_sup:stop(), + Error + end. entities() -> entities([agent, manager], []). diff --git a/lib/snmp/src/app/snmp_app_sup.erl b/lib/snmp/src/app/snmp_app_sup.erl index eb89cc5b6d..bb6faa18cb 100644 --- a/lib/snmp/src/app/snmp_app_sup.erl +++ b/lib/snmp/src/app/snmp_app_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2023. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -64,7 +64,12 @@ start_agent(Type, Opts) -> "~n Type: ~p" "~n Opts: ~p", [Type, Opts]), Restart = get_restart(Opts, permanent), - start_sup_child(snmpa_supervisor, Restart, [Type, Opts]). + case start_sup_child(snmpa_supervisor, Restart, [Type, Opts]) of + {ok, Pid} = OK when is_pid(Pid) -> + OK; + {error, {Reason, _ChildSpec}} -> + {error, Reason} + end. start_manager(Type, Opts) -> @@ -72,7 +77,12 @@ start_manager(Type, Opts) -> "~n Type: ~p" "~n Opts: ~p", [Type, Opts]), Restart = get_restart(Opts, transient), - start_sup_child(snmpm_supervisor, Restart, [Type, Opts]). + case start_sup_child(snmpm_supervisor, Restart, [Type, Opts]) of + {ok, Pid} = OK when is_pid(Pid) -> + OK; + {error, {Reason, _ChildSpec}} -> + {error, Reason} + end. %%%------------------------------------------------------------------- diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index 87dd624306..6e952a4584 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -647,7 +647,7 @@ signature_scheme(SignAlgo) when is_integer(SignAlgo) -> signature_scheme(_) -> unassigned. signature_schemes_1_2(SigAlgs) -> - lists:foldl(fun(Alg, Acc) when is_atom(Alg) -> + lists:reverse(lists:foldl(fun(Alg, Acc) when is_atom(Alg) -> case scheme_to_components(Alg) of {Hash, Sign = rsa_pss_pss,_} -> [{Hash, Sign} | Acc]; @@ -658,7 +658,7 @@ signature_schemes_1_2(SigAlgs) -> end; (Alg, Acc) -> [Alg| Acc] - end, [], SigAlgs). + end, [], SigAlgs)). %% TODO: reserved code points? diff --git a/lib/ssl/src/ssl_gen_statem.erl b/lib/ssl/src/ssl_gen_statem.erl index 36768ab6c7..62c09c60ae 100644 --- a/lib/ssl/src/ssl_gen_statem.erl +++ b/lib/ssl/src/ssl_gen_statem.erl @@ -1897,7 +1897,8 @@ connection_info(#state{handshake_env = #handshake_env{sni_hostname = SNIHostname security_info(#state{connection_states = ConnectionStates, static_env = #static_env{role = Role}, - ssl_options = #{keep_secrets := KeepSecrets}}) -> + ssl_options = #{keep_secrets := KeepSecrets}, + protocol_specific = ProtocolSpecific}) -> ReadState = ssl_record:current_connection_state(ConnectionStates, read), #{security_parameters := #security_parameters{client_random = ClientRand, @@ -1911,10 +1912,12 @@ security_info(#state{connection_states = ConnectionStates, BaseSecurityInfo; true -> #{security_parameters := - #security_parameters{application_traffic_secret = AppTrafSecretWrite, - client_early_data_secret = ClientEarlyData - }} = + #security_parameters{ + application_traffic_secret = AppTrafSecretWrite0, + client_early_data_secret = ClientEarlyData}} = ssl_record:current_connection_state(ConnectionStates, write), + Sender = maps:get(sender, ProtocolSpecific, undefined), + AppTrafSecretWrite = {Sender, AppTrafSecretWrite0}, if Role == server -> if ServerEarlyData =/= undefined -> [{server_traffic_secret_0, AppTrafSecretWrite}, @@ -2118,8 +2121,25 @@ maybe_add_keylog({_, 'tlsv1.2'}, Info) -> maybe_add_keylog({_, 'tlsv1.3'}, Info) -> try {client_random, ClientRandomBin} = lists:keyfind(client_random, 1, Info), - {client_traffic_secret_0, ClientTrafficSecret0Bin} = lists:keyfind(client_traffic_secret_0, 1, Info), - {server_traffic_secret_0, ServerTrafficSecret0Bin} = lists:keyfind(server_traffic_secret_0, 1, Info), + %% after traffic key update current traffic secret + %% is stored in tls_sender process state + MaybeUpdateTrafficSecret = + fun({Direction, {Sender, TrafficSecret0}}) -> + TrafficSecret = + case call(Sender, get_application_traffic_secret) of + {ok, SenderAppTrafSecretWrite} -> + SenderAppTrafSecretWrite; + _ -> + TrafficSecret0 + end, + {Direction, TrafficSecret}; + (TrafficSecret0) -> + TrafficSecret0 + end, + {client_traffic_secret_0, ClientTrafficSecret0Bin} = + MaybeUpdateTrafficSecret(lists:keyfind(client_traffic_secret_0, 1, Info)), + {server_traffic_secret_0, ServerTrafficSecret0Bin} = + MaybeUpdateTrafficSecret(lists:keyfind(server_traffic_secret_0, 1, Info)), {client_handshake_traffic_secret, ClientHSecretBin} = lists:keyfind(client_handshake_traffic_secret, 1, Info), {server_handshake_traffic_secret, ServerHSecretBin} = lists:keyfind(server_handshake_traffic_secret, 1, Info), {selected_cipher_suite, #{prf := Prf}} = lists:keyfind(selected_cipher_suite, 1, Info), diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index b53a801b90..b531b51819 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -313,6 +313,13 @@ connection({call, From}, {dist_handshake_complete, _Node, DHandle}, {next_event, internal, {application_packets, {self(),undefined}, Data}}]} end; +connection({call, From}, get_application_traffic_secret, State) -> + CurrentWrite = maps:get(current_write, State#data.connection_states), + SecurityParams = maps:get(security_parameters, CurrentWrite), + ApplicationTrafficSecret = + SecurityParams#security_parameters.application_traffic_secret, + hibernate_after(?FUNCTION_NAME, State, + [{reply, From, {ok, ApplicationTrafficSecret}}]); connection(internal, {application_packets, From, Data}, StateData) -> send_application_data(Data, From, ?FUNCTION_NAME, StateData); connection(internal, {post_handshake_data, From, HSData}, StateData) -> diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index def13d0860..70c0b50d4a 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -41,7 +41,8 @@ aes_decipher_good/1, aes_decipher_fail/0, aes_decipher_fail/1, - padding_test/1 + padding_test/1, + sign_algorithms/1 ]). @@ -49,7 +50,7 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- all() -> - [aes_decipher_good, aes_decipher_fail, padding_test]. + [aes_decipher_good, aes_decipher_fail, padding_test, sign_algorithms]. groups() -> []. @@ -122,6 +123,10 @@ padding_test(Config) when is_list(Config) -> pad_test(HashSz, CipherState, {3,2}), pad_test(HashSz, CipherState, {3,3}). +%%-------------------------------------------------------------------- +sign_algorithms(Config) when is_list(Config) -> + [{sha256,rsa_pss_pss},{rsa,sha256}] = ssl_cipher:signature_schemes_1_2([rsa_pss_pss_sha256, {rsa, sha256}]). + %%-------------------------------------------------------------------- % Internal functions -------------------------------------------------------- %%-------------------------------------------------------------------- diff --git a/lib/ssl/test/ssl_key_update_SUITE.erl b/lib/ssl/test/ssl_key_update_SUITE.erl index baa408dbc5..0ac3228f85 100644 --- a/lib/ssl/test/ssl_key_update_SUITE.erl +++ b/lib/ssl/test/ssl_key_update_SUITE.erl @@ -42,7 +42,6 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("ssl/src/ssl_api.hrl"). -include_lib("ssl/src/ssl_connection.hrl"). - all() -> [{group, 'tlsv1.3'}]. @@ -107,24 +106,22 @@ key_update_at_server(Config) -> key_update_at(Config, Role) -> Data = "123456789012345", %% 15 bytes Server = ssl_test_lib:start_server(erlang, - [{options, [{key_update_at, 14}]}], + [{options, [{keep_secrets, true}, + {key_update_at, 14}]}], Config), Port = ssl_test_lib:inet_port(Server), - {Client, - #sslsocket{pid = - [ClientReceiverPid, ClientSenderPid]}} = - ssl_test_lib:start_client(erlang, - [return_socket, {port, Port}, - {options, [{key_update_at, 14}]}], - Config), + ClientResult = ssl_test_lib:start_client(erlang, + [return_socket, {port, Port}, + {options, [{keep_secrets, true}, + {key_update_at, 14}]}], + Config), + {Client, ClientSocket} = ClientResult, Server ! get_socket, - #sslsocket{pid = - [ServerReceiverPid, ServerSenderPid]} = - receive - {Server, {socket, S}} -> S - end, - Keys0 = get_keys(ClientReceiverPid, ClientSenderPid, - ServerReceiverPid, ServerSenderPid), + ServerSocket = receive + {Server, {socket, S}} -> S + end, + Keys0 = get_traffic_secrets(ClientSocket, ServerSocket), + ct:log("connected", []), {Sender, Receiver} = case Role of client -> {Client, Server}; server -> {Server, Client} @@ -134,53 +131,67 @@ key_update_at(Config, Role) -> Data = ssl_test_lib:check_active_receive(Receiver, Data), %% TODO check if key has been updated (needs debug logging of secrets) ct:sleep(500), - Keys1 = get_keys(ClientReceiverPid, ClientSenderPid, - ServerReceiverPid, ServerSenderPid), + ct:log("sent and waited", []), + Keys1 = get_traffic_secrets(ClientSocket, ServerSocket), verify_key_update(Keys0, Keys1), %% Test mechanism to prevent infinite loop of key updates BigData = binary:copy(<<"1234567890">>, 10), %% 100 bytes ok = ssl_test_lib:send(Sender, BigData), ct:sleep(500), - Keys2 = get_keys(ClientReceiverPid, ClientSenderPid, - ServerReceiverPid, ServerSenderPid), + ct:log("sent and waited 2", []), + Keys2 = get_traffic_secrets(ClientSocket, ServerSocket), verify_key_update(Keys1, Keys2), ssl_test_lib:close(Server), ssl_test_lib:close(Client). -get_keys(ClientReceiverPid, ClientSenderPid, - ServerReceiverPid, ServerSenderPid) -> - F = fun(Pid) -> - {connection, D} = sys:get_state(Pid), - M0 = element(3, D), - Cr = maps:get(current_write, M0), - {Pid, {maps:get(security_parameters, Cr), - maps:get(cipher_state, Cr)}} +get_traffic_secrets(ClientSocket, ServerSocket) -> + ProcessSocket = + fun(Socket, Role) -> + {ok, [{keylog, KeyLog}]} = ssl:connection_information(Socket, [keylog]), + Interesting = + fun(S) -> + Patterns = ["CLIENT_TRAFFIC_SECRET", "SERVER_TRAFFIC_SECRET"], + SearchResults = [string:find(S, P) || P <- Patterns], + lists:any(fun(I) -> I /= nomatch end, SearchResults) + end, + TrafficSecrets = lists:filter(Interesting, KeyLog), + Print = fun(Secret) -> + [Name, _A, B] = string:lexemes(Secret, " "), + [Key] = io_lib:format("~s", [B]), + {Name, {Role, Key}} + end, + [Print(Scr) || Scr <- TrafficSecrets] end, - SendersKeys = [F(P) || P <- [ClientSenderPid, ServerSenderPid]], - - G = fun(Pid) -> - {connection, D} = sys:get_state(Pid), - #state{connection_states = Cs} = D, - Cr = maps:get(current_read, Cs), - {Pid, {maps:get(security_parameters,Cr), - maps:get(cipher_state, Cr)}} + Secrets = lists:flatten( + [ProcessSocket(S, R) || + {S, R} <- + [{ClientSocket, client}, {ServerSocket, server}]]), + P = fun(Direction) -> + Vals = proplists:get_all_values(Direction, Secrets), + ct:log("~30s ~10s(c) ~10s(s)", + [Direction, proplists:get_value(client, Vals), + proplists:get_value(server, Vals)]), + {Direction, [proplists:get_value(client, Vals), + proplists:get_value(server, Vals)]} end, - ReceiversKeys = [G(P) || P <- [ClientReceiverPid, ServerReceiverPid]], - maps:from_list(SendersKeys ++ ReceiversKeys). + [P(Direction) || + Direction <- + ["CLIENT_TRAFFIC_SECRET_0", "SERVER_TRAFFIC_SECRET_0"]]. verify_key_update(Keys0, Keys1) -> - V = fun(Pid, CurrentKeys) -> - BaseKeys = maps:get(Pid, Keys0), - ct:log("Pid = ~p~nBaseKeys = ~p~nCurrentKeys = ~p", - [Pid, BaseKeys, CurrentKeys], [esc_chars]), - case BaseKeys == CurrentKeys of - true -> - ct:fail("Keys don't differ for ~w", [Pid]); - false -> - ok - end - end, - maps:foreach(V, Keys1). + CTS0 = proplists:get_value("CLIENT_TRAFFIC_SECRET_0", Keys0), + CTS1 = proplists:get_value("CLIENT_TRAFFIC_SECRET_0", Keys1), + STS0 = proplists:get_value("SERVER_TRAFFIC_SECRET_0", Keys0), + STS1 = proplists:get_value("SERVER_TRAFFIC_SECRET_0", Keys1), + CTS = lists:zip(CTS0, CTS1), + STS = lists:zip(STS0, STS1), + Pred = fun({A, B}) when A == B -> + ct:fail(no_key_update), + false; + (_) -> + true + end, + [true = lists:all(Pred, X) || X <- [CTS, STS]]. explicit_key_update() -> [{doc,"Test ssl:update_key/2 between erlang client and erlang server."}]. diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index f97ea99e60..6228c5857a 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -661,6 +661,7 @@ Erlang code. -type abstract_expr() :: af_literal() | af_match(abstract_expr()) + | af_maybe_match() | af_variable() | af_tuple(abstract_expr()) | af_nil() @@ -687,7 +688,9 @@ Erlang code. | af_local_fun() | af_remote_fun() | af_fun() - | af_named_fun(). + | af_named_fun() + | af_maybe() + | af_maybe_else(). -type af_record_update(T) :: {'record', anno(), @@ -830,6 +833,9 @@ Erlang code. -type af_map_pattern() :: {'map', anno(), [af_assoc_exact(af_pattern())]}. +-type af_maybe() :: {'maybe', anno(), af_body()}. +-type af_maybe_else() :: {'maybe', anno(), af_body(), {'else', anno(), af_clause_seq()}}. + -type abstract_type() :: af_annotated_type() | af_atom() | af_bitstring_type() @@ -940,6 +946,8 @@ Erlang code. -type af_match(T) :: {'match', anno(), af_pattern(), T}. +-type af_maybe_match() :: {'maybe_match', anno(), af_pattern(), abstract_expr()}. + -type af_variable() :: {'var', anno(), atom()}. % | af_anon_variable() %-type af_anon_variable() :: {'var', anno(), '_'}. @@ -965,7 +973,7 @@ Erlang code. -type binary_op() :: '/' | '*' | 'div' | 'rem' | 'band' | 'and' | '+' | '-' | 'bor' | 'bxor' | 'bsl' | 'bsr' | 'or' | 'xor' | '++' | '--' | '==' | '/=' | '=<' | '<' | '>=' | '>' | '=:=' - | '=/='. + | '=/=' | '!'. -type af_unary_op(T) :: {'op', anno(), unary_op(), T}. diff --git a/make/autoconf/otp.m4 b/make/autoconf/otp.m4 index 98b8ea5f61..a522fae3bd 100644 --- a/make/autoconf/otp.m4 +++ b/make/autoconf/otp.m4 @@ -1458,7 +1458,7 @@ AC_DEFUN(ETHR_CHK_GCC_ATOMIC_OPS, [ ethr_cv_arm_isb_sy_instr=no AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[ - __asm__ __volatile__("isb sy" : : : "memory"); + __asm__ __volatile__("isb sy\n" : : : "memory"); ]])],[ethr_cv_arm_isb_sy_instr=yes],[]) ]) if test $ethr_cv_arm_isb_sy_instr = yes; then @@ -1468,7 +1468,7 @@ AC_DEFUN(ETHR_CHK_GCC_ATOMIC_OPS, [ ethr_cv_arm_dc_cvau_instr=no AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[ - char data[512]; __asm__ __volatile__("dc cvau, %0" : "r" (data) : : "memory"); + char data[512]; __asm__ __volatile__("dc cvau, %0\n" :: "r" (data) : "memory"); ]])],[ethr_cv_arm_dc_cvau_instr=yes],[]) ]) if test $ethr_cv_arm_dc_cvau_instr = yes; then @@ -1478,7 +1478,7 @@ AC_DEFUN(ETHR_CHK_GCC_ATOMIC_OPS, [ ethr_cv_arm_ic_ivau_instr=no AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[ - char data[512]; __asm__ __volatile__("ic ivau, %0" : "r" (data) : : "memory"); + char data[512]; __asm__ __volatile__("ic ivau, %0\n" :: "r" (data) : "memory"); ]])],[ethr_cv_arm_ic_ivau_instr=yes],[]) ]) if test $ethr_cv_arm_ic_ivau_instr = yes; then diff --git a/make/otp_version_tickets b/make/otp_version_tickets index 23c2246bcb..b8220e1a87 100644 --- a/make/otp_version_tickets +++ b/make/otp_version_tickets @@ -1,44 +1 @@ -OTP-16607 -OTP-18316 -OTP-18348 -OTP-18356 -OTP-18366 -OTP-18371 -OTP-18372 -OTP-18373 -OTP-18374 -OTP-18375 -OTP-18378 -OTP-18380 -OTP-18381 -OTP-18382 -OTP-18384 -OTP-18387 -OTP-18390 -OTP-18391 -OTP-18392 -OTP-18393 -OTP-18398 -OTP-18404 -OTP-18406 -OTP-18407 -OTP-18408 -OTP-18411 -OTP-18412 -OTP-18418 -OTP-18426 -OTP-18429 -OTP-18432 -OTP-18434 -OTP-18443 -OTP-18446 -OTP-18454 -OTP-18457 -OTP-18458 -OTP-18460 -OTP-18463 -OTP-18469 -OTP-18470 -OTP-18472 -OTP-18475 -OTP-18496 +DEVELOPMENT diff --git a/otp_versions.table b/otp_versions.table index 413efad49c..33572d8f01 100644 --- a/otp_versions.table +++ b/otp_versions.table @@ -12,6 +12,7 @@ OTP-25.0.3 : erts-13.0.3 ssl-10.8.3 # asn1-5.0.19 common_test-1.23 compiler-8.2 OTP-25.0.2 : erts-13.0.2 ssl-10.8.2 # asn1-5.0.19 common_test-1.23 compiler-8.2 crypto-5.1.1 debugger-5.3 dialyzer-5.0.1 diameter-2.2.6 edoc-1.2 eldap-1.2.10 erl_docgen-1.3 erl_interface-5.3 et-1.6.5 eunit-2.7.1 ftp-1.1.1 inets-8.0 jinterface-1.13 kernel-8.4.1 megaco-4.4 mnesia-4.21.1 observer-2.12 odbc-2.14 os_mon-2.7.1 parsetools-2.4 public_key-1.13 reltool-0.9 runtime_tools-1.19 sasl-4.2 snmp-5.13 ssh-4.14.1 stdlib-4.0.1 syntax_tools-3.0 tftp-1.0.3 tools-3.5.3 wx-2.2 xmerl-1.3.29 : OTP-25.0.1 : crypto-5.1.1 dialyzer-5.0.1 erts-13.0.1 kernel-8.4.1 mnesia-4.21.1 ssh-4.14.1 ssl-10.8.1 stdlib-4.0.1 # asn1-5.0.19 common_test-1.23 compiler-8.2 debugger-5.3 diameter-2.2.6 edoc-1.2 eldap-1.2.10 erl_docgen-1.3 erl_interface-5.3 et-1.6.5 eunit-2.7.1 ftp-1.1.1 inets-8.0 jinterface-1.13 megaco-4.4 observer-2.12 odbc-2.14 os_mon-2.7.1 parsetools-2.4 public_key-1.13 reltool-0.9 runtime_tools-1.19 sasl-4.2 snmp-5.13 syntax_tools-3.0 tftp-1.0.3 tools-3.5.3 wx-2.2 xmerl-1.3.29 : OTP-25.0 : asn1-5.0.19 common_test-1.23 compiler-8.2 crypto-5.1 debugger-5.3 dialyzer-5.0 diameter-2.2.6 edoc-1.2 erl_docgen-1.3 erl_interface-5.3 erts-13.0 eunit-2.7.1 inets-8.0 jinterface-1.13 kernel-8.4 megaco-4.4 mnesia-4.21 observer-2.12 odbc-2.14 parsetools-2.4 public_key-1.13 runtime_tools-1.19 sasl-4.2 snmp-5.13 ssh-4.14 ssl-10.8 stdlib-4.0 syntax_tools-3.0 tools-3.5.3 wx-2.2 xmerl-1.3.29 # eldap-1.2.10 et-1.6.5 ftp-1.1.1 os_mon-2.7.1 reltool-0.9 tftp-1.0.3 : +OTP-24.3.4.10 : erts-12.3.2.10 inets-7.5.3.3 snmp-5.12.0.3 # asn1-5.0.18.1 common_test-1.22.1.1 compiler-8.1.1.3 crypto-5.0.6.3 debugger-5.2.1 dialyzer-4.4.4.1 diameter-2.2.5 edoc-1.1 eldap-1.2.10 erl_docgen-1.2.1 erl_interface-5.2.2 et-1.6.5 eunit-2.7 ftp-1.1.1 jinterface-1.12.2 kernel-8.3.2.3 megaco-4.3 mnesia-4.20.4.2 observer-2.11.1 odbc-2.13.5 os_mon-2.7.1 parsetools-2.3.2 public_key-1.12.0.1 reltool-0.9 runtime_tools-1.18 sasl-4.1.2 ssh-4.13.2.2 ssl-10.7.3.6 stdlib-3.17.2.2 syntax_tools-2.6 tftp-1.0.3 tools-3.5.2 wx-2.1.4 xmerl-1.3.28 : OTP-24.3.4.9 : compiler-8.1.1.3 erts-12.3.2.9 inets-7.5.3.2 ssh-4.13.2.2 ssl-10.7.3.6 # asn1-5.0.18.1 common_test-1.22.1.1 crypto-5.0.6.3 debugger-5.2.1 dialyzer-4.4.4.1 diameter-2.2.5 edoc-1.1 eldap-1.2.10 erl_docgen-1.2.1 erl_interface-5.2.2 et-1.6.5 eunit-2.7 ftp-1.1.1 jinterface-1.12.2 kernel-8.3.2.3 megaco-4.3 mnesia-4.20.4.2 observer-2.11.1 odbc-2.13.5 os_mon-2.7.1 parsetools-2.3.2 public_key-1.12.0.1 reltool-0.9 runtime_tools-1.18 sasl-4.1.2 snmp-5.12.0.2 stdlib-3.17.2.2 syntax_tools-2.6 tftp-1.0.3 tools-3.5.2 wx-2.1.4 xmerl-1.3.28 : OTP-24.3.4.8 : erts-12.3.2.8 snmp-5.12.0.2 # asn1-5.0.18.1 common_test-1.22.1.1 compiler-8.1.1.2 crypto-5.0.6.3 debugger-5.2.1 dialyzer-4.4.4.1 diameter-2.2.5 edoc-1.1 eldap-1.2.10 erl_docgen-1.2.1 erl_interface-5.2.2 et-1.6.5 eunit-2.7 ftp-1.1.1 inets-7.5.3.1 jinterface-1.12.2 kernel-8.3.2.3 megaco-4.3 mnesia-4.20.4.2 observer-2.11.1 odbc-2.13.5 os_mon-2.7.1 parsetools-2.3.2 public_key-1.12.0.1 reltool-0.9 runtime_tools-1.18 sasl-4.1.2 ssh-4.13.2.1 ssl-10.7.3.5 stdlib-3.17.2.2 syntax_tools-2.6 tftp-1.0.3 tools-3.5.2 wx-2.1.4 xmerl-1.3.28 : OTP-24.3.4.7 : common_test-1.22.1.1 compiler-8.1.1.2 erts-12.3.2.7 kernel-8.3.2.3 mnesia-4.20.4.2 snmp-5.12.0.1 stdlib-3.17.2.2 # asn1-5.0.18.1 crypto-5.0.6.3 debugger-5.2.1 dialyzer-4.4.4.1 diameter-2.2.5 edoc-1.1 eldap-1.2.10 erl_docgen-1.2.1 erl_interface-5.2.2 et-1.6.5 eunit-2.7 ftp-1.1.1 inets-7.5.3.1 jinterface-1.12.2 megaco-4.3 observer-2.11.1 odbc-2.13.5 os_mon-2.7.1 parsetools-2.3.2 public_key-1.12.0.1 reltool-0.9 runtime_tools-1.18 sasl-4.1.2 ssh-4.13.2.1 ssl-10.7.3.5 syntax_tools-2.6 tftp-1.0.3 tools-3.5.2 wx-2.1.4 xmerl-1.3.28 : |