diff options
Diffstat (limited to 'lib')
53 files changed, 940 insertions, 190 deletions
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml index 26640acabc..5399528271 100644 --- a/lib/asn1/doc/src/notes.xml +++ b/lib/asn1/doc/src/notes.xml @@ -32,6 +32,40 @@ <p>This document describes the changes made to the asn1 application.</p> +<section><title>Asn1 5.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Default values now work in extension for PER, so if you + give the atom <c>asn1_DEFAULT</c> instead of a value it + will become the default value.</p> + <p> + Own Id: OTP-13011 Aux Id: ERIERL-60 </p> + </item> + </list> + </section> + +</section> + +<section><title>Asn1 5.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed compilation error of generated code caused by a + missing quotation of function names as part of an + external call for encoding.</p> + <p> + Own Id: OTP-14519 Aux Id: ERIERL-49 </p> + </item> + </list> + </section> + +</section> + <section><title>Asn1 5.0</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/asn1/src/asn1ct_constructed_per.erl b/lib/asn1/src/asn1ct_constructed_per.erl index 3f1be4febb..aff383479b 100644 --- a/lib/asn1/src/asn1ct_constructed_per.erl +++ b/lib/asn1/src/asn1ct_constructed_per.erl @@ -985,9 +985,11 @@ gen_enc_components_call1(Gen, TopType, [C|Rest], DynamicEnc, Ext) -> Imm1; 'OPTIONAL' -> enc_absent(Gen, Element, [asn1_NOVALUE], Imm1); - {'DEFAULT',Def} -> + {'DEFAULT',Def} when Ext =:= noext -> DefValues = def_values(Type, Def), - enc_absent(Gen, Element, DefValues, Imm1) + enc_absent(Gen, Element, DefValues, Imm1); + {'DEFAULT',_} -> + enc_absent(Gen, Element, [asn1_DEFAULT], Imm1) end, Imm = case Imm2 of [] -> []; diff --git a/lib/asn1/src/asn1ct_gen_per.erl b/lib/asn1/src/asn1ct_gen_per.erl index 28b4e46b0c..82e9326294 100644 --- a/lib/asn1/src/asn1ct_gen_per.erl +++ b/lib/asn1/src/asn1ct_gen_per.erl @@ -101,7 +101,7 @@ gen_encode_user(Erules,D) when is_record(D,typedef) -> #'Externaltypereference'{module=CurrMod,type=Etype} -> emit([{asis,enc_func(Etype)},"(Val).",nl]); #'Externaltypereference'{module=Emod,type=Etype} -> - emit([{asis,Emod},":",enc_func(Etype),"(Val).",nl]) + emit([{asis,Emod},":",{asis,enc_func(Etype)},"(Val).",nl]) end. diff --git a/lib/asn1/src/asn1rtt_per_common.erl b/lib/asn1/src/asn1rtt_per_common.erl index 2ecc9e4bc7..5b5f47dfee 100644 --- a/lib/asn1/src/asn1rtt_per_common.erl +++ b/lib/asn1/src/asn1rtt_per_common.erl @@ -542,6 +542,7 @@ extension_bitmap(_Val, Pos, Limit, Acc) when Pos >= Limit -> extension_bitmap(Val, Pos, Limit, Acc) -> Bit = case element(Pos, Val) of asn1_NOVALUE -> 0; + asn1_DEFAULT -> 0; _ -> 1 end, extension_bitmap(Val, Pos+1, Limit, (Acc bsl 1) bor Bit). diff --git a/lib/asn1/test/Makefile b/lib/asn1/test/Makefile index f4041fa89b..c38d1c6ebd 100644 --- a/lib/asn1/test/Makefile +++ b/lib/asn1/test/Makefile @@ -43,6 +43,7 @@ MODULES= \ testChoTypeRefSet \ testConstraints \ testDef \ + testExtensionDefault \ testOpt \ testSeqDefault \ testSeqExtension \ diff --git a/lib/asn1/test/asn1_SUITE.erl b/lib/asn1/test/asn1_SUITE.erl index 5fe6945ff2..69f226bcc0 100644 --- a/lib/asn1/test/asn1_SUITE.erl +++ b/lib/asn1/test/asn1_SUITE.erl @@ -147,6 +147,7 @@ groups() -> testImport, testDER, testDEFAULT, + testExtensionDefault, testMvrasn6, testContextSwitchingTypes, testOpenTypeImplicitTag, @@ -444,6 +445,12 @@ testDEFAULT(Config, Rule, Opts) -> testDef:main(Rule), testSeqSetDefaultVal:main(Rule, Opts). +testExtensionDefault(Config) -> + test(Config, fun testExtensionDefault/3). +testExtensionDefault(Config, Rule, Opts) -> + asn1_test_lib:compile_all(["ExtensionDefault"], Config, [Rule|Opts]), + testExtensionDefault:main(Rule). + testMaps(Config) -> test(Config, fun testMaps/3, [{ber,[maps,no_ok_wrapper]}, diff --git a/lib/asn1/test/asn1_SUITE_data/ExtensionDefault.asn1 b/lib/asn1/test/asn1_SUITE_data/ExtensionDefault.asn1 new file mode 100644 index 0000000000..67d9cb6312 --- /dev/null +++ b/lib/asn1/test/asn1_SUITE_data/ExtensionDefault.asn1 @@ -0,0 +1,12 @@ +ExtensionDefault DEFINITIONS AUTOMATIC TAGS ::= + +BEGIN + +Message ::= SEQUENCE { + id INTEGER (0..5), + ..., + priority Priority DEFAULT low +} +Priority ::= ENUMERATED { low(0), high(1), ... } + +END diff --git a/lib/asn1/test/asn1_SUITE_data/ImportsFrom.asn1 b/lib/asn1/test/asn1_SUITE_data/ImportsFrom.asn1 index 32b8f75dde..dee3cd5048 100644 --- a/lib/asn1/test/asn1_SUITE_data/ImportsFrom.asn1 +++ b/lib/asn1/test/asn1_SUITE_data/ImportsFrom.asn1 @@ -1,8 +1,9 @@ ImportsFrom DEFINITIONS AUTOMATIC TAGS ::= BEGIN -IMPORTS Int FROM ImportsFrom2; +IMPORTS Int, Quoted-Seq FROM ImportsFrom2; i Int ::= 42 +My-Seq ::= Quoted-Seq END diff --git a/lib/asn1/test/asn1_SUITE_data/ImportsFrom2.asn1 b/lib/asn1/test/asn1_SUITE_data/ImportsFrom2.asn1 index b0c29d24ae..a8e619e215 100644 --- a/lib/asn1/test/asn1_SUITE_data/ImportsFrom2.asn1 +++ b/lib/asn1/test/asn1_SUITE_data/ImportsFrom2.asn1 @@ -2,6 +2,11 @@ ImportsFrom2 DEFINITIONS AUTOMATIC TAGS ::= BEGIN IMPORTS Int FROM ImportsFrom3; +Quoted-Seq ::= SEQUENCE { + x INTEGER(0..17), + y INTEGER(0..666) +} + LocalDef ::= OCTET STRING END diff --git a/lib/asn1/test/testExtensionDefault.erl b/lib/asn1/test/testExtensionDefault.erl new file mode 100644 index 0000000000..cc50fa95b8 --- /dev/null +++ b/lib/asn1/test/testExtensionDefault.erl @@ -0,0 +1,53 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2017. 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(testExtensionDefault). + +-export([main/1]). + +main(_Erule) -> + roundtrip('Message', {'Message',1,low}), %Will be explicitly encoded. + roundtrip('Message', {'Message',1,high}), + roundtrip('Message', {'Message',1,asn1_DEFAULT}, {'Message',1,low}), + + map_roundtrip('Message', #{id=>1,priority=>low}), %Will be explicitly encoded. + map_roundtrip('Message', #{id=>1,priority=>high}), + map_roundtrip('Message', #{id=>1}, #{id=>1,priority=>low}), + ok. + +roundtrip(Type, Value) -> + asn1_test_lib:roundtrip('ExtensionDefault', Type, Value). + +roundtrip(Type, Value, Expected) -> + %% asn1_test_lib:roundtrip/3 will invoke map_roundtrip/3, which will + %% not work in this case. Therefore, implement the roundtrip ourselves. + M = 'ExtensionDefault', + {ok,Enc} = M:encode(Type, Value), + {ok,Expected} = M:decode(Type, Enc), + ok. + +map_roundtrip(Type, Value) -> + map_roundtrip(Type, Value, Value). + +map_roundtrip(Type, Value, Expected) -> + M = 'maps_ExtensionDefault', + Enc = M:encode(Type, Value), + Expected = M:decode(Type, Enc), + ok. diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk index 7329a9f879..5900f3037e 100644 --- a/lib/asn1/vsn.mk +++ b/lib/asn1/vsn.mk @@ -1 +1 @@ -ASN1_VSN = 5.0 +ASN1_VSN = 5.0.2 diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml index 28b2d44168..37a1846160 100644 --- a/lib/common_test/doc/src/notes.xml +++ b/lib/common_test/doc/src/notes.xml @@ -33,6 +33,26 @@ <file>notes.xml</file> </header> +<section><title>Common_Test 1.15.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + In OTP-20.0, the behavior of c, make, and ct_make was + changed so that in some cases the beam files by default + would be written to the directory where the source files + were found. This is now changed back to the old behavior + so beam files are by default written to current + directory.</p> + <p> + Own Id: OTP-14489 Aux Id: ERL-438 </p> + </item> + </list> + </section> + +</section> + <section><title>Common_Test 1.15</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/common_test/src/ct_make.erl b/lib/common_test/src/ct_make.erl index 4d66796b83..220cb0473d 100644 --- a/lib/common_test/src/ct_make.erl +++ b/lib/common_test/src/ct_make.erl @@ -280,15 +280,47 @@ recompile(File, NoExec, Load, Opts) -> do_recompile(_File, true, _Load, _Opts) -> out_of_date; -do_recompile(File, false, noload, Opts) -> +do_recompile(File, false, Load, Opts) -> io:format("Recompile: ~ts\n",[File]), - compile:file(File, [report_errors, report_warnings, error_summary |Opts]); -do_recompile(File, false, load, Opts) -> - io:format("Recompile: ~ts\n",[File]), - c:c(File, Opts); -do_recompile(File, false, netload, Opts) -> - io:format("Recompile: ~ts\n",[File]), - c:nc(File, Opts). + case compile:file(File, [report_errors, report_warnings |Opts]) of + Ok when is_tuple(Ok), element(1,Ok)==ok -> + maybe_load(element(2,Ok), Load, Opts); + _Error -> + error + end. + +maybe_load(_Mod, noload, _Opts) -> + ok; +maybe_load(Mod, Load, Opts) -> + %% We have compiled File with options Opts. Find out where the + %% output file went to, and load it. + case compile:output_generated(Opts) of + true -> + Dir = proplists:get_value(outdir,Opts,"."), + do_load(Dir, Mod, Load); + false -> + io:format("** Warning: No object file created - nothing loaded **~n"), + ok + end. + +do_load(Dir, Mod, load) -> + code:purge(Mod), + case code:load_abs(filename:join(Dir, Mod),Mod) of + {module,Mod} -> + {ok,Mod}; + Other -> + Other + end; +do_load(Dir, Mod, netload) -> + Obj = atom_to_list(Mod) ++ code:objfile_extension(), + Fname = filename:join(Dir, Obj), + case file:read_file(Fname) of + {ok,Bin} -> + rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]), + {ok,Mod}; + Other -> + Other + end. exists(File) -> case file:read_file_info(File) of diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk index a219aa4736..9fc3f7f797 100644 --- a/lib/common_test/vsn.mk +++ b/lib/common_test/vsn.mk @@ -1 +1 @@ -COMMON_TEST_VSN = 1.15 +COMMON_TEST_VSN = 1.15.1 diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index f3d42a909b..bc335a9eaa 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,23 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.1.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fail labels on guard BIFs weren't taken into account + during an optimization pass, and a bug in the validation + pass sometimes prevented this from being noticed when a + fault occurred.</p> + <p> + Own Id: OTP-14522 Aux Id: ERIERL-48 </p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.1</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl index cc6e54ca16..e39fbdc3b7 100644 --- a/lib/compiler/src/beam_utils.erl +++ b/lib/compiler/src/beam_utils.erl @@ -789,39 +789,48 @@ live_opt([{recv_mark,_}=I|Is], Regs, D, Acc) -> live_opt([], _, _, Acc) -> Acc. -live_opt_block([{set,Ds,Ss,Op}=I0|Is], Regs0, D, Acc) -> +live_opt_block([{set,Ds,Ss,Op0}|Is], Regs0, D, Acc) -> Regs1 = x_live(Ss, x_dead(Ds, Regs0)), - {I,Regs} = case Op of - {alloc,Live0,Alloc} -> - %% The life-time analysis used by the code generator - %% is sometimes too conservative, so it may be - %% possible to lower the number of live registers - %% based on the exact liveness information. - %% The main benefit is that more optimizations that - %% depend on liveness information (such as the - %% beam_bool and beam_dead passes) may be applied. - Live = live_regs(Regs1), - true = Live =< Live0, %Assertion. - I1 = {set,Ds,Ss,{alloc,Live,Alloc}}, - {I1,live_call(Live)}; - _ -> - {I0,Regs1} - end, + {Op, Regs} = live_opt_block_op(Op0, Regs1, D), + I = {set, Ds, Ss, Op}, + case Ds of - [{x,X}] -> - case (not is_live(X, Regs0)) andalso Op =:= move of - true -> - live_opt_block(Is, Regs0, D, Acc); - false -> - live_opt_block(Is, Regs, D, [I|Acc]) - end; - _ -> - live_opt_block(Is, Regs, D, [I|Acc]) + [{x,X}] -> + case (not is_live(X, Regs0)) andalso Op =:= move of + true -> + live_opt_block(Is, Regs0, D, Acc); + false -> + live_opt_block(Is, Regs, D, [I|Acc]) + end; + _ -> + live_opt_block(Is, Regs, D, [I|Acc]) end; live_opt_block([{'%live',_,_}|Is], Regs, D, Acc) -> live_opt_block(Is, Regs, D, Acc); live_opt_block([], Regs, _, Acc) -> {Acc,Regs}. +live_opt_block_op({alloc,Live0,AllocOp}, Regs0, D) -> + Regs = + case AllocOp of + {Kind, _N, Fail} when Kind =:= gc_bif; Kind =:= put_map -> + live_join_label(Fail, D, Regs0); + _ -> + Regs0 + end, + + %% The life-time analysis used by the code generator is sometimes too + %% conservative, so it may be possible to lower the number of live + %% registers based on the exact liveness information. The main benefit is + %% that more optimizations that depend on liveness information (such as the + %% beam_bool and beam_dead passes) may be applied. + Live = live_regs(Regs), + true = Live =< Live0, + {{alloc,Live,AllocOp}, live_call(Live)}; +live_opt_block_op({bif,_N,Fail} = Op, Regs, D) -> + {Op, live_join_label(Fail, D, Regs)}; +live_opt_block_op(Op, Regs, _D) -> + {Op, Regs}. + live_join_labels([{f,L}|T], D, Regs0) when L =/= 0 -> Regs = gb_trees:get(L, D) bor Regs0, live_join_labels(T, D, Regs); diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index f726625510..622e00bb2b 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -928,9 +928,9 @@ verify_call_match_context(Lbl, Ctx, #vst{ft=Ft}) -> error({unsuitable_bs_start_match2,I}) end. -allocate(Zero, Stk, Heap, Live, #vst{current=#st{numy=none}=St}=Vst0) -> +allocate(Zero, Stk, Heap, Live, #vst{current=#st{numy=none}}=Vst0) -> verify_live(Live, Vst0), - Vst = prune_x_regs(Live, Vst0), + Vst = #vst{current=St} = prune_x_regs(Live, Vst0), Ys = init_regs(Stk, case Zero of true -> initialized; false -> uninitialized diff --git a/lib/compiler/test/beam_utils_SUITE.erl b/lib/compiler/test/beam_utils_SUITE.erl index a3f1bb93fe..710cb050d4 100644 --- a/lib/compiler/test/beam_utils_SUITE.erl +++ b/lib/compiler/test/beam_utils_SUITE.erl @@ -260,6 +260,14 @@ otp_8949_b(A, B) -> liveopt(_Config) -> F = liveopt_fun(42, pebkac, user), void = F(42, #alarmInfo{type=sctp,cause=pebkac,origin=user}), + + + A = {#alarmInfo{cause = {abc, def}}, ghi}, + A = liveopt_guard_bif(A), + + B = {#alarmInfo{cause = {abc}}, def}, + {#alarmInfo{cause = {{abc}}}, def} = liveopt_guard_bif(B), + ok. liveopt_fun(Peer, Cause, Origin) -> @@ -271,6 +279,15 @@ liveopt_fun(Peer, Cause, Origin) -> void end. +liveopt_guard_bif({#alarmInfo{cause=F}=R, X}=A) -> + %% ERIERL-48 + if + is_tuple(F), tuple_size(F) == 2 -> A; + true -> + R2 = R#alarmInfo{cause={F}}, + {R2,X} + end. + %% Thanks to QuickCheck. coverage(_Config) -> 42+7 = merchant([[],7,false]), diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 463c264a5f..27ee5a3fb7 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.1 +COMPILER_VSN = 7.1.1 diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index 0d2cb6c4df..c26b7aab5e 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -32,6 +32,21 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 3.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Fix a bug where merging PLT:s could lose info. The + bug was introduced in Erlang/OTP 20.0. </p> + <p> + Own Id: OTP-14558 Aux Id: ERIERL-53 </p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 3.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl index 47994fc35b..0fd99bbc04 100644 --- a/lib/dialyzer/src/dialyzer_plt.erl +++ b/lib/dialyzer/src/dialyzer_plt.erl @@ -772,6 +772,7 @@ tab_is_disj(K1, T1, T2) -> end. merge_tables(T1, T2) -> + ets:safe_fixtable(T1, true), tab_merge(ets:first(T1), T1, T2). tab_merge('$end_of_table', T1, T2) -> diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index 4a1a7c25a0..866a82ee3e 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 3.2 +DIALYZER_VSN = 3.2.1 diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index d74635fc01..edf8731a82 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -279,7 +279,18 @@ requests defined by <c>max_keep_alive_requests</c>, the server closes the connection. The server closes it even if there are queued request. Default is no limit.</p> - </item> + </item> + + + <tag><marker id="max_client_body_chunk"></marker>{max_client_body_chunk, integer()}</tag> + <item> + <p>Enforces chunking of a HTTP PUT or POST body data to be deliverd + to the mod_esi callback. Note this is not supported for mod_cgi. + Default is no limit e.i the whole body is deliverd as one entity, which could + be very memory consuming. <seealso marker="mod_esi">mod_esi(3)</seealso>. + </p> + </item> + </taglist> <marker id="props_admin"></marker> diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml index 46cc796c8a..e2f0758cdf 100644 --- a/lib/inets/doc/src/mod_esi.xml +++ b/lib/inets/doc/src/mod_esi.xml @@ -121,35 +121,61 @@ <funcs> <func> - <name>Module:Function(SessionID, Env, Input)-> _ </name> + <name>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> <v>SessionID = term()</v> <v>Env = env()</v> - <v>Input = string()</v> + <v>Input = string() | chunked_data()</v> + <v>chunked_data() = {first, Data::binary()} | + {continue, Data::binary(), State::term()} | + {last, Data::binary(), State::term()} </v> + <v>State = term()</v> </type> <desc> <p><c>Module</c> must be found in the code path and export <c>Function</c> with an arity of three. An <c>erlScriptAlias</c> must also be set up in the configuration file for the web server.</p> - <p>If the HTTP request is a 'post' request and a body is sent, - <c>content_length</c> is the length of the posted - data. If 'get' is used, <c>query_string</c> is the data after - <em>?</em> in the URL.</p> - <p><c>ParsedHeader</c> is the HTTP request as a key-value tuple - list. The keys in <c>ParsedHeader</c> are in lower case.</p> - <p><c>SessionID</c> is an identifier - the server uses when <c>deliver/2</c> is called. Do not - assume anything about the datatype.</p> - <p>Use this callback function to generate dynamic web - content dynamically. When a part of the page is generated, send the - data back to the client through <c>deliver/2</c>. Notice - that the first chunk of data sent to the client must at - least contain all HTTP header fields that the response - will generate. If the first chunk does not contain the - <em>end of HTTP header</em>, that is, <c>"\r\n\r\n",</c> - the server assumes that no HTTP header fields will be generated.</p> + + <p><c>mod_esi:deliver/2</c> shall be used to generate the response + to the client and <c>SessionID</c> is an identifier that shall by used when + calling this function, do not assume anything about + the datatype. This function may be called + several times to chunk the the respons data. Notice that the + first chunk of data sent to the client must at least contain + all HTTP header fields that the response will generate. If the + first chunk does not contain the <em>end of HTTP header</em>, + that is, <c>"\r\n\r\n",</c> the server assumes that no HTTP + header fields will be generated.</p> + + <p><c>Env</c> environment data of the request see description above.</p> + + <p><c>Input</c> is query data of a GET request or the body of + a PUT or POST request. The default behavior (legacy reasons) + for delivering the body, is that the whole body is gathered and + converted to a string. But if the httpd config parameter + <seealso + marker="httpd#max_client_body_chunk">max_client_body_chunk</seealso> + is set, the body will be delivered as binary chunks + instead. The maximum size of the chunks is either <seealso + marker="httpd#max_client_body_chunk">max_client_body_chunk</seealso> + or decide by the client if it uses HTTP chunked encoding + to send the body. When using the chunking + mechanism this callback must return {continue, State::term()} + for all calls where <c>Input</c> is <c>{first, + Data::binary()}</c> or <c>{continue, Data::binary(), + State::term()}</c>. When <c>Input</c> is <c>{last, + Data::binary(), State::term()}</c> the return value will be ignored. + <note><p>Note that if the body is + small all data may be delivered in only one chunk and then the + callback will be called with {last, Data::binary(), undefined} + without getting called with <c>{first, + Data::binary()}</c>.</p></note> The input <c>State</c> is + the last returned <c>State</c>, in it the callback can include + any data that it needs to keep track of when handling the chunks. + </p> + <p></p> </desc> </func> @@ -159,14 +185,13 @@ This function is deprecated and is only kept for backwards compatibility.</fsummary> <type> <v>Env = env()</v> - <v>Input = string()</v> + <v>Input = string() </v> <v>Response = string()</v> </type> <desc> <p>This callback format consumes much memory, as the whole response must be generated before it is sent to the - user. This function is deprecated and is only kept for backwards - compatibility. + user. This callback format is deprecated. For new development, use <c>Module:Function/3</c>.</p> </desc> </func> diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index c893b10dca..45b6deba97 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -22,7 +22,7 @@ -export([print/1]). -export([get/2, put/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2]). --export([newformat/3]). +-export([newformat/3, post_chunked/3]). %% These are used by the inets test-suite -export([delay/1, chunk_timeout/3]). @@ -131,15 +131,31 @@ footer() -> "</BODY> </HTML>\n". - -newformat(SessionID, _Env, _Input)-> +post_chunked(_SessionID, _Env, {first, _Body} = _Bodychunk) -> + {continue, {state, 1}}; +post_chunked(_SessionID, _Env, {continue, _Body, {state, N}} = _Bodychunk) -> + {continue, {state, N+1}}; +post_chunked(SessionID, _Env, {last, _Body, {state, N}} = _Bodychunk) -> + mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"), + mod_esi:deliver(SessionID, top("Received chunked body")), + mod_esi:deliver(SessionID, "Received" ++ integer_to_list(N) ++ "chunks"), + mod_esi:deliver(SessionID, footer()); +post_chunked(SessionID, _Env, {last, _Body, undefined} = _Bodychunk) -> + mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"), + mod_esi:deliver(SessionID, top("Received chunked body")), + mod_esi:deliver(SessionID, "Received 1 chunk"), + mod_esi:deliver(SessionID, footer()); +post_chunked(_, _, _Body) -> + exit(body_not_chunked). + +newformat(SessionID,_,_) -> mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"), mod_esi:deliver(SessionID, top("new esi format test")), mod_esi:deliver(SessionID, "This new format is nice<BR>"), mod_esi:deliver(SessionID, "This new format is nice<BR>"), mod_esi:deliver(SessionID, "This new format is nice<BR>"), mod_esi:deliver(SessionID, footer()). - + %% ------------------------------------------------------ delay(Time) when is_integer(Time) -> diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl index 749f58c197..0eaf073255 100644 --- a/lib/inets/src/http_server/httpd_request.erl +++ b/lib/inets/src/http_server/httpd_request.erl @@ -36,7 +36,7 @@ %% little at a time on a socket. -export([ parse_method/1, parse_uri/1, parse_version/1, parse_headers/1, - whole_body/1 + whole_body/1, body_chunk_first/3, body_chunk/3, add_chunk/1 ]). @@ -76,13 +76,12 @@ body_data(Headers, Body) -> ContentLength = list_to_integer(Headers#http_request_h.'content-length'), case size(Body) - ContentLength of 0 -> - {binary_to_list(Body), <<>>}; + {Body, <<>>}; _ -> <<BodyThisReq:ContentLength/binary, Next/binary>> = Body, - {binary_to_list(BodyThisReq), Next} + {BodyThisReq, Next} end. - %%------------------------------------------------------------------------- %% validate(Method, Uri, Version) -> ok | {error, {bad_request, Reason} | %% {error, {not_supported, {Method, Uri, Version}} @@ -292,10 +291,46 @@ parse_headers(<<Octet, Rest/binary>>, Header, Headers, Current, parse_headers(Rest, [Octet | Header], Headers, Current + 1, Max, Options, Result). +body_chunk_first(Body, 0 = Length, _) -> + whole_body(Body, Length); +body_chunk_first(Body, Length, MaxChunk) -> + case body_chunk(Body, Length, MaxChunk) of + {ok, {last, NewBody}} -> + {ok, NewBody}; + Other -> + Other + end. +%% Used to chunk non chunk decoded post/put data +add_chunk([<<>>, Body, Length, MaxChunk]) -> + body_chunk(Body, Length, MaxChunk); +add_chunk([More, Body, Length, MaxChunk]) -> + body_chunk(<<Body/binary, More/binary>>, Length, MaxChunk). + +body_chunk(<<>> = Body, Length, MaxChunk) -> + {ok, {continue, ?MODULE, add_chunk, [Body, Length, MaxChunk]}}; +body_chunk(Body, Length, nolimit) -> + whole_body(Body, Length); + +body_chunk(Body, Length, MaxChunk) when Length > MaxChunk -> + case size(Body) >= MaxChunk of + true -> + <<Chunk:MaxChunk/binary, Rest/binary>> = Body, + {ok, {{continue, Chunk}, ?MODULE, add_chunk, [Rest, Length - MaxChunk, MaxChunk]}}; + false -> + {ok, {continue, ?MODULE, add_chunk, [Body, Length, MaxChunk]}} + end; +body_chunk(Body, Length, MaxChunk) -> + case size(Body) of + Length -> + {ok, {last, Body}}; + _ -> + {ok, {continue, ?MODULE, add_chunk, [Body, Length, MaxChunk]}} + end. + whole_body(Body, Length) -> case size(Body) of N when N < Length, Length > 0 -> - {?MODULE, whole_body, [Body, Length]}; + {?MODULE, add_chunk, [Body, Length, nolimit]}; N when N >= Length, Length >= 0 -> %% When a client uses pipelining trailing data %% may be part of the next request! @@ -443,6 +478,3 @@ check_header({"content-length", Value}, Maxsizes) -> end; check_header(_, _) -> ok. - - - diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl index 538d52b98d..bd4fdd3832 100644 --- a/lib/inets/src/http_server/httpd_request_handler.erl +++ b/lib/inets/src/http_server/httpd_request_handler.erl @@ -49,7 +49,8 @@ headers, %% #http_request_h{} body, %% binary() data, %% The total data received in bits, checked after 10s - byte_limit %% Bit limit per second before kick out + byte_limit, %% Bit limit per second before kick out + chunk }). %%==================================================================== @@ -124,7 +125,8 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> NrOfRequest = max_keep_alive_request(ConfigDB), MaxContentLen = max_content_length(ConfigDB), Customize = customize(ConfigDB), - + MaxChunk = max_client_body_chunk(ConfigDB), + {_, Status} = httpd_manager:new_connection(Manager), MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, @@ -139,7 +141,8 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) -> status = Status, timeout = TimeOut, max_keep_alive_request = NrOfRequest, - mfa = MFA}, + mfa = MFA, + chunk = chunk_start(MaxChunk)}, http_transport:setopts(SocketType, Socket, [binary, {packet, 0}, {active, once}]), @@ -194,6 +197,7 @@ handle_cast(Msg, #state{mod = ModData} = State) -> %%-------------------------------------------------------------------- handle_info({Proto, Socket, Data}, #state{mfa = {Module, Function, Args}, + chunk = {ChunkState, _}, mod = #mod{socket_type = SockType, socket = Socket} = ModData} = State) when (((Proto =:= tcp) orelse @@ -207,7 +211,8 @@ handle_info({Proto, Socket, Data}, _ -> State#state.data + byte_size(Data) end, - case PROCESSED of + + case PROCESSED of {ok, Result} -> NewState = case NewDataSize of undefined -> @@ -215,7 +220,7 @@ handle_info({Proto, Socket, Data}, _ -> set_new_data_size(cancel_request_timeout(State), NewDataSize) end, - handle_http_msg(Result, NewState); + handle_msg(Result, NewState); {error, {size_error, MaxSize, ErrCode, ErrStr}, Version} -> NewModData = ModData#mod{http_version = Version}, httpd_response:send_status(NewModData, ErrCode, ErrStr), @@ -224,7 +229,10 @@ handle_info({Proto, Socket, Data}, error_log(Reason, NewModData), {stop, normal, State#state{response_sent = true, mod = NewModData}}; - + + {http_chunk = Module, Function, Args} when ChunkState =/= undefined -> + NewState = handle_chunk(Module, Function, Args, State), + {noreply, NewState}; NewMFA -> http_transport:setopts(SockType, Socket, [{active, once}]), case NewDataSize of @@ -349,6 +357,34 @@ await_socket_ownership_transfer(AcceptTimeout) -> exit(accept_socket_timeout) end. + +%%% Internal chunking of client body +handle_msg({{continue, Chunk}, Module, Function, Args}, #state{chunk = {_, CbState}} = State) -> + handle_internal_chunk(State#state{chunk = {continue, CbState}, + body = Chunk}, Module, Function, Args); +handle_msg({continue, Module, Function, Args}, #state{mod = ModData} = State) -> + http_transport:setopts(ModData#mod.socket_type, + ModData#mod.socket, + [{active, once}]), + {noreply, State#state{mfa = {Module, Function, Args}}}; +handle_msg({last, Body}, #state{headers = Headers, chunk = {_, CbState}} = State) -> + NewHeaders = Headers#http_request_h{'content-length' = integer_to_list(size(Body))}, + handle_response(State#state{chunk = {last, CbState}, + headers = NewHeaders, + body = Body}); +%%% Last data chunked by client +handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {ChunkState, CbState}} = State) when ChunkState =/= undefined -> + NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), + handle_response(State#state{chunk = {last, CbState}, + headers = NewHeaders, + body = Body}); +handle_msg({ChunkedHeaders, Body}, #state{headers = Headers , chunk = {undefined, _}} = State) -> + NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), + handle_response(State#state{headers = NewHeaders, + body = Body}); +handle_msg(Result, State) -> + handle_http_msg(Result, State). + handle_http_msg({_, _, Version, {_, _}, _}, #state{status = busy, mod = ModData} = State) -> handle_manager_busy(State#state{mod = @@ -405,10 +441,6 @@ handle_http_msg({Method, Uri, Version, {RecordHeaders, Headers}, Body}, error_log(Reason, ModData), {stop, normal, State#state{response_sent = true}} end; -handle_http_msg({ChunkedHeaders, Body}, - State = #state{headers = Headers}) -> - NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), - handle_response(State#state{headers = NewHeaders, body = Body}); handle_http_msg(Body, State) -> handle_response(State#state{body = Body}). @@ -443,22 +475,25 @@ handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) -> end. -handle_body(#state{headers = Headers, body = Body, mod = ModData} = State, +handle_body(#state{headers = Headers, body = Body, + chunk = {ChunkState, CbState}, mod = #mod{config_db = ConfigDB} = ModData} = State, MaxHeaderSize, MaxBodySize) -> + MaxChunk = max_client_body_chunk(ConfigDB), case Headers#http_request_h.'transfer-encoding' of "chunked" -> try http_chunk:decode(Body, MaxBodySize, MaxHeaderSize) of - {Module, Function, Args} -> + {Module, Function, Args} -> http_transport:setopts(ModData#mod.socket_type, ModData#mod.socket, [{active, once}]), {noreply, State#state{mfa = - {Module, Function, Args}}}; - {ok, {ChunkedHeaders, NewBody}} -> - NewHeaders = - http_chunk:handle_headers(Headers, ChunkedHeaders), - handle_response(State#state{headers = NewHeaders, - body = NewBody}) + {Module, Function, Args}, + chunk = chunk_start(MaxChunk)}}; + {ok, {ChunkedHeaders, NewBody}} -> + NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), + handle_response(State#state{headers = NewHeaders, + body = NewBody, + chunk = chunk_finish(ChunkState, CbState, MaxChunk)}) catch throw:Error -> httpd_response:send_status(ModData, 400, @@ -476,21 +511,25 @@ handle_body(#state{headers = Headers, body = Body, mod = ModData} = State, error_log(Reason, ModData), {stop, normal, State#state{response_sent = true}}; _ -> - Length = list_to_integer(Headers#http_request_h.'content-length'), + Length = list_to_integer(Headers#http_request_h.'content-length'), + MaxChunk = max_client_body_chunk(ConfigDB), case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of true -> - case httpd_request:whole_body(Body, Length) of - {Module, Function, Args} -> - http_transport:setopts(ModData#mod.socket_type, + case httpd_request:body_chunk_first(Body, Length, MaxChunk) of + {ok, {continue, Module, Function, Args}} -> + http_transport:setopts(ModData#mod.socket_type, ModData#mod.socket, [{active, once}]), {noreply, State#state{mfa = {Module, Function, Args}}}; - - {ok, NewBody} -> - handle_response( - State#state{headers = Headers, - body = NewBody}) + {ok, {{continue, Chunk}, Module, Function, Args}} -> + handle_internal_chunk(State#state{chunk = chunk_start(MaxChunk), + body = Chunk}, Module, Function, Args); + {ok, NewBody} -> + handle_response(State#state{chunk = chunk_finish(ChunkState, + CbState, MaxChunk), + headers = Headers, + body = NewBody}) end; false -> httpd_response:send_status(ModData, 413, "Body too long"), @@ -550,15 +589,61 @@ expect(Headers, _, ConfigDB) -> end end. +handle_chunk(http_chunk = Module, decode_data = Function, + [ChunkSize, TotalChunk, {MaxBodySize, BodySoFar, _AccLength, MaxHeaderSize}], + #state{chunk = {_, CbState}, + mod = #mod{socket_type = SockType, + socket = Socket} = ModData} = State) -> + {continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = + {continue, BodySoFar, CbState}}), + http_transport:setopts(SockType, Socket, [{active, once}]), + State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, [ChunkSize, TotalChunk, {MaxBodySize, <<>>, 0, MaxHeaderSize}]}}; + +handle_chunk(http_chunk = Module, decode_size = Function, + [Data, HexList, _AccSize, {MaxBodySize, BodySoFar, _AccLength, MaxHeaderSize}], + #state{chunk = {_, CbState}, + mod = #mod{socket_type = SockType, + socket = Socket} = ModData} = State) -> + {continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = {continue, BodySoFar, CbState}}), + http_transport:setopts(SockType, Socket, [{active, once}]), + State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, [Data, HexList, 0, {MaxBodySize, <<>>, 0, MaxHeaderSize}]}}; +handle_chunk(Module, Function, Args, #state{mod = #mod{socket_type = SockType, + socket = Socket}} = State) -> + http_transport:setopts(SockType, Socket, [{active, once}]), + State#state{mfa = {Module, Function, Args}}. + +handle_internal_chunk(#state{chunk = {ChunkState, CbState}, body = Chunk, + mod = #mod{socket_type = SockType, + socket = Socket} = ModData} = State, Module, Function, Args)-> + Bodychunk = body_chunk(ChunkState, CbState, Chunk), + {continue, NewCbState} = httpd_response:handle_continuation(ModData#mod{entity_body = Bodychunk}), + case Args of + [<<>> | _] -> + http_transport:setopts(SockType, Socket, [{active, once}]), + {noreply, State#state{chunk = {continue, NewCbState}, mfa = {Module, Function, Args}}}; + _ -> + handle_info({dummy, Socket, <<>>}, State#state{chunk = {continue, NewCbState}, + mfa = {Module, Function, Args}}) + end. + +handle_response(#state{body = Body, + headers = Headers, + mod = ModData, + chunk = {last, CbState}, + max_keep_alive_request = Max} = State) when Max > 0 -> + {NewBody, Data} = httpd_request:body_data(Headers, Body), + ok = httpd_response:generate_and_send_response( + ModData#mod{entity_body = {last, NewBody, CbState}}), + handle_next_request(State#state{response_sent = true}, Data); handle_response(#state{body = Body, mod = ModData, headers = Headers, max_keep_alive_request = Max} = State) when Max > 0 -> {NewBody, Data} = httpd_request:body_data(Headers, Body), + %% Backwards compatible, may cause memory explosion ok = httpd_response:generate_and_send_response( - ModData#mod{entity_body = NewBody}), + ModData#mod{entity_body = binary_to_list(NewBody)}), handle_next_request(State#state{response_sent = true}, Data); - handle_response(#state{body = Body, headers = Headers, mod = ModData} = State) -> @@ -578,6 +663,7 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData, MaxURISize = max_uri_size(ModData#mod.config_db), MaxContentLen = max_content_length(ModData#mod.config_db), Customize = customize(ModData#mod.config_db), + MaxChunk = max_client_body_chunk(ModData#mod.config_db), MFA = {httpd_request, parse, [[{max_uri, MaxURISize}, {max_header, MaxHeaderSize}, {max_version, ?HTTP_MAX_VERSION_STRING}, @@ -590,6 +676,7 @@ handle_next_request(#state{mod = #mod{connection = true} = ModData, max_keep_alive_request = decrease(Max), headers = undefined, body = undefined, + chunk = chunk_start(MaxChunk), response_sent = false}, NewState = activate_request_timeout(TmpState), @@ -647,6 +734,9 @@ error_log(ReasonString, #mod{config_db = ConfigDB}) -> max_header_size(ConfigDB) -> httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE). +max_client_body_chunk(ConfigDB) -> + httpd_util:lookup(ConfigDB, max_client_body_chunk, nolimit). + max_uri_size(ConfigDB) -> httpd_util:lookup(ConfigDB, max_uri_size, ?HTTP_MAX_URI_SIZE). @@ -661,3 +751,17 @@ max_content_length(ConfigDB) -> customize(ConfigDB) -> httpd_util:lookup(ConfigDB, customize, httpd_custom). + +chunk_start(nolimit) -> + {undefined, undefined}; +chunk_start(_) -> + {first, undefined}. +chunk_finish(_, _, nolimit) -> + {undefined, undefined}; +chunk_finish(_, CbState, _) -> + {last, CbState}. + +body_chunk(first, _, Chunk) -> + {first, Chunk}; +body_chunk(ChunkState, CbState, Chunk) -> + {ChunkState, Chunk, CbState}. diff --git a/lib/inets/src/http_server/httpd_response.erl b/lib/inets/src/http_server/httpd_response.erl index effa273e92..6b9053fda6 100644 --- a/lib/inets/src/http_server/httpd_response.erl +++ b/lib/inets/src/http_server/httpd_response.erl @@ -21,7 +21,7 @@ -module(httpd_response). -export([generate_and_send_response/1, send_status/3, send_header/3, send_body/3, send_chunk/3, send_final_chunk/2, send_final_chunk/3, - split_header/2, is_disable_chunked_send/1, cache_headers/2]). + split_header/2, is_disable_chunked_send/1, cache_headers/2, handle_continuation/1]). -export([map_status_code/2]). -include_lib("inets/src/inets_app/inets_internal.hrl"). @@ -31,6 +31,9 @@ -define(VMODULE,"RESPONSE"). +handle_continuation(Mod) -> + generate_and_send_response(Mod). + %% If peername does not exist the client already discarded the %% request so we do not need to send a reply. generate_and_send_response(#mod{init_data = @@ -39,6 +42,8 @@ generate_and_send_response(#mod{init_data = generate_and_send_response(#mod{config_db = ConfigDB} = ModData) -> Modules = httpd_util:lookup(ConfigDB, modules, ?DEFAULT_MODS), case traverse_modules(ModData, Modules) of + {continue, _} = Continue -> + Continue; done -> ok; {proceed, Data} -> @@ -69,17 +74,15 @@ generate_and_send_response(#mod{config_db = ConfigDB} = ModData) -> traverse_modules(ModData,[]) -> {proceed,ModData#mod.data}; traverse_modules(ModData,[Module|Rest]) -> - ?hdrd("traverse modules", [{callback_module, Module}]), try apply(Module, do, [ModData]) of + {continue, _} = Continue -> + Continue; done -> - ?hdrt("traverse modules - done", []), - done; + done; {break, NewData} -> - ?hdrt("traverse modules - break", [{new_data, NewData}]), - {proceed, NewData}; + {proceed, NewData}; {proceed, NewData} -> - ?hdrt("traverse modules - proceed", [{new_data, NewData}]), - traverse_modules(ModData#mod{data = NewData}, Rest) + traverse_modules(ModData#mod{data = NewData}, Rest) catch T:E -> String = @@ -104,15 +107,10 @@ send_status(#mod{socket_type = SocketType, socket = Socket, config_db = ConfigDB} = ModData, StatusCode, PhraseArgs) -> - ?hdrd("send status", [{status_code, StatusCode}, - {phrase_args, PhraseArgs}]), - ReasonPhrase = httpd_util:reason_phrase(StatusCode), Message = httpd_util:message(StatusCode, PhraseArgs, ConfigDB), Body = get_body(ReasonPhrase, Message), - ?hdrt("send status - header", [{reason_phrase, ReasonPhrase}, - {message, Message}]), send_header(ModData, StatusCode, [{content_type, "text/html"}, {content_length, integer_to_list(length(Body))}]), diff --git a/lib/inets/src/http_server/httpd_script_env.erl b/lib/inets/src/http_server/httpd_script_env.erl index e15613273e..055f08fdb0 100644 --- a/lib/inets/src/http_server/httpd_script_env.erl +++ b/lib/inets/src/http_server/httpd_script_env.erl @@ -74,9 +74,13 @@ which_peercert(#mod{socket_type = {Type, _}, socket = Socket}) when Type == essl which_peercert(_) -> %% Not an ssl connection undefined. + which_resolve(#mod{init_data = #init_data{resolve = Resolve}}) -> Resolve. +which_name(#mod{config_db = ConfigDB}) -> + httpd_util:lookup(ConfigDB, server_name). + which_method(#mod{method = Method}) -> Method. @@ -85,7 +89,8 @@ which_request_uri(#mod{request_uri = RUri}) -> create_basic_elements(esi, ModData) -> [{server_software, which_server(ModData)}, - {server_name, which_resolve(ModData)}, + {server_name, which_name(ModData)}, + {host_name, which_resolve(ModData)}, {gateway_interface, ?GATEWAY_INTERFACE}, {server_protocol, ?SERVER_PROTOCOL}, {server_port, which_port(ModData)}, @@ -96,7 +101,8 @@ create_basic_elements(esi, ModData) -> create_basic_elements(cgi, ModData) -> [{"SERVER_SOFTWARE", which_server(ModData)}, - {"SERVER_NAME", which_resolve(ModData)}, + {"SERVER_NAME", which_name(ModData)}, + {"HOST_NAME", which_resolve(ModData)}, {"GATEWAY_INTERFACE", ?GATEWAY_INTERFACE}, {"SERVER_PROTOCOL", ?SERVER_PROTOCOL}, {"SERVER_PORT", integer_to_list(which_port(ModData))}, diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index b21af1418c..3a589ca5f0 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -31,7 +31,6 @@ -include("httpd.hrl"). -include("httpd_internal.hrl"). --include("inets_internal.hrl"). -define(VMODULE,"ESI"). -define(DEFAULT_ERL_TIMEOUT,15000). @@ -69,7 +68,6 @@ deliver(_SessionID, _Data) -> %% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS %%------------------------------------------------------------------------- do(ModData) -> - ?hdrt("do", []), case proplists:get_value(status, ModData#mod.data) of {_StatusCode, _PhraseArgs, _Reason} -> {proceed, ModData#mod.data}; @@ -190,7 +188,6 @@ store({erl_script_nocache, Value}, _) -> %%% Internal functions %%%======================================================================== generate_response(ModData) -> - ?hdrt("generate response", []), case scheme(ModData#mod.request_uri, ModData#mod.config_db) of {eval, ESIBody, Modules} -> eval(ModData, ESIBody, Modules); @@ -242,7 +239,6 @@ alias_match_str(Alias, eval_script_alias) -> erl(#mod{method = Method} = ModData, ESIBody, Modules) when (Method =:= "GET") orelse (Method =:= "HEAD") orelse (Method =:= "DELETE") -> - ?hdrt("erl", [{method, Method}]), case httpd_util:split(ESIBody,":|%3A|/",2) of {ok, [ModuleName, FuncAndInput]} -> case httpd_util:split(FuncAndInput,"[\?/]",2) of @@ -273,14 +269,12 @@ erl(#mod{method = "PUT", entity_body = Body} = ModData, generate_webpage(ModData, ESIBody, Modules, list_to_atom(ModuleName), FunctionName, {Input,Body}, - [{entity_body, Body} | - script_elements(FuncAndInput, Input)]); + script_elements(FuncAndInput, Input)); {ok, [FunctionName]} -> generate_webpage(ModData, ESIBody, Modules, list_to_atom(ModuleName), FunctionName, {undefined,Body}, - [{entity_body, Body} | - script_elements(FuncAndInput, "")]); + script_elements(FuncAndInput, "")); {ok, BadRequest} -> {proceed,[{status,{400,none, BadRequest}} | ModData#mod.data]} @@ -290,12 +284,11 @@ erl(#mod{method = "PUT", entity_body = Body} = ModData, end; erl(#mod{method = "POST", entity_body = Body} = ModData, ESIBody, Modules) -> - ?hdrt("erl", [{method, post}]), case httpd_util:split(ESIBody,":|%3A|/",2) of {ok,[ModuleName, Function]} -> generate_webpage(ModData, ESIBody, Modules, list_to_atom(ModuleName), - Function, Body, [{entity_body, Body}]); + Function, Body, []); {ok, BadRequest} -> {proceed,[{status, {400, none, BadRequest}} | ModData#mod.data]} end; @@ -304,7 +297,6 @@ erl(#mod{request_uri = ReqUri, method = "PATCH", http_version = Version, data = Data}, _ESIBody, _Modules) -> - ?hdrt("erl", [{method, patch}]), {proceed, [{status,{501,{"PATCH", ReqUri, Version}, ?NICE("Erl mechanism doesn't support method PATCH")}}| Data]}. @@ -315,7 +307,6 @@ generate_webpage(ModData, ESIBody, [all], Module, FunctionName, FunctionName, Input, ScriptElements); generate_webpage(ModData, ESIBody, Modules, Module, FunctionName, Input, ScriptElements) -> - ?hdrt("generate webpage", []), Function = list_to_atom(FunctionName), case lists:member(Module, Modules) of true -> @@ -337,7 +328,6 @@ generate_webpage(ModData, ESIBody, Modules, Module, FunctionName, %% Old API that waits for the dymnamic webpage to be totally generated %% before anythig is sent back to the client. erl_scheme_webpage_whole(Mod, Func, Env, Input, ModData) -> - ?hdrt("erl_scheme_webpage_whole", [{module, Mod}, {function, Func}]), case (catch Mod:Func(Env, Input)) of {'EXIT',{undef, _}} -> {proceed, [{status, {404, ModData#mod.request_uri, "Not found"}} @@ -375,7 +365,6 @@ erl_scheme_webpage_whole(Mod, Func, Env, Input, ModData) -> %% in small chunks at the time during generation. erl_scheme_webpage_chunk(Mod, Func, Env, Input, ModData) -> process_flag(trap_exit, true), - ?hdrt("erl_scheme_webpage_chunk", [{module, Mod}, {function, Func}]), Self = self(), %% Spawn worker that generates the webpage. %% It would be nicer to use erlang:function_exported/3 but if the @@ -386,7 +375,9 @@ erl_scheme_webpage_chunk(Mod, Func, Env, Input, ModData) -> {'EXIT', {undef,_}} -> %% Will force fallback on the old API exit(erl_scheme_webpage_chunk_undefined); - _ -> + {continue, _} = Continue -> + exit(Continue); + _ -> ok end end), @@ -400,13 +391,12 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid) -> deliver_webpage_chunk(ModData, Pid, Timeout). deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> - ?hdrt("deliver_webpage_chunk", [{timeout, Timeout}]), case receive_headers(Timeout) of {error, Reason} -> %% Happens when webpage generator callback/3 is undefined - ?hdrv("deliver_webpage_chunk - failed receiving headers", - [{reason, Reason}]), {error, Reason}; + {continue, _} = Continue -> + Continue; {Headers, Body} -> case httpd_esi:handle_headers(Headers) of {proceed, AbsPath} -> @@ -430,7 +420,6 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> IsDisableChunkedSend) end; timeout -> - ?hdrv("deliver_webpage_chunk - timeout", []), send_headers(ModData, 504, [{"connection", "close"}]), httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket), {proceed,[{response, {already_sent, 200, 0}} | ModData#mod.data]} @@ -439,16 +428,14 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) -> receive_headers(Timeout) -> receive {esi_data, Chunk} -> - ?hdrt("receive_headers - received esi data (esi)", []), httpd_esi:parse_headers(lists:flatten(Chunk)); {ok, Chunk} -> - ?hdrt("receive_headers - received esi data (ok)", []), httpd_esi:parse_headers(lists:flatten(Chunk)); {'EXIT', Pid, erl_scheme_webpage_chunk_undefined} when is_pid(Pid) -> - ?hdrd("receive_headers - exit:chunk-undef", []), {error, erl_scheme_webpage_chunk_undefined}; - {'EXIT', Pid, Reason} when is_pid(Pid) -> - ?hdrv("receive_headers - exit", [{reason, Reason}]), + {'EXIT', Pid, {continue, _} = Continue} when is_pid(Pid) -> + Continue; + {'EXIT', Pid, Reason} when is_pid(Pid) -> exit({mod_esi_linked_process_died, Pid, Reason}) after Timeout -> timeout @@ -463,7 +450,6 @@ handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, _) -> {proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]}; handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) -> - ?hdrt("handle_body - send chunk", [{timeout, Timeout}, {size, Size}]), httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend), receive {esi_data, Data} when is_binary(Data) -> @@ -543,7 +529,6 @@ eval(#mod{request_uri = ReqUri, method = "PUT", http_version = Version, data = Data}, _ESIBody, _Modules) -> - ?hdrt("eval", [{method, put}]), {proceed,[{status,{501,{"PUT", ReqUri, Version}, ?NICE("Eval mechanism doesn't support method PUT")}}| Data]}; @@ -552,7 +537,6 @@ eval(#mod{request_uri = ReqUri, method = "DELETE", http_version = Version, data = Data}, _ESIBody, _Modules) -> - ?hdrt("eval", [{method, delete}]), {proceed,[{status,{501,{"DELETE", ReqUri, Version}, ?NICE("Eval mechanism doesn't support method DELETE")}}| Data]}; @@ -561,14 +545,12 @@ eval(#mod{request_uri = ReqUri, method = "POST", http_version = Version, data = Data}, _ESIBody, _Modules) -> - ?hdrt("eval", [{method, post}]), {proceed,[{status,{501,{"POST", ReqUri, Version}, ?NICE("Eval mechanism doesn't support method POST")}}| Data]}; eval(#mod{method = Method} = ModData, ESIBody, Modules) when (Method =:= "GET") orelse (Method =:= "HEAD") -> - ?hdrt("eval", [{method, Method}]), case is_authorized(ESIBody, Modules) of true -> case generate_webpage(ESIBody) of diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 055b847319..916c26b28a 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -73,6 +73,7 @@ all() -> {group, http_reload}, {group, https_reload}, {group, http_mime_types}, + {group, http_post}, mime_types_format ]. @@ -98,6 +99,7 @@ groups() -> {https_security, [], [{group, security}]}, {http_reload, [], [{group, reload}]}, {https_reload, [], [{group, reload}]}, + {http_post, [], [{group, post}]}, {http_mime_types, [], [alias_1_1, alias_1_0, alias_0_9]}, {limit, [], [max_clients_1_1, max_clients_1_0, max_clients_0_9]}, {custom, [], [customize, add_default]}, @@ -110,6 +112,7 @@ groups() -> disturbing_1_0, disturbing_0_9 ]}, + {post, [], [chunked_post, chunked_chunked_encoded_post]}, {basic_auth, [], [basic_auth_1_1, basic_auth_1_0, basic_auth_0_9]}, {auth_api, [], [auth_api_1_1, auth_api_1_0, auth_api_0_9 ]}, @@ -148,6 +151,7 @@ http_get() -> ipv6 ]. + load() -> [light, medium %%,heavy @@ -214,6 +218,7 @@ init_per_group(Group, Config0) when Group == http_basic; Group == http_auth_api_mnesia; Group == http_security; Group == http_reload; + Group == http_post; Group == http_mime_types -> ok = start_apps(Group), @@ -266,6 +271,7 @@ end_per_group(Group, _Config) when Group == http_basic; Group == http_htaccess; Group == http_security; Group == http_reload; + Group == http_post; Group == http_mime_types -> inets:stop(); @@ -290,7 +296,7 @@ end_per_group(_, _) -> %%-------------------------------------------------------------------- init_per_testcase(Case, Config) when Case == host; Case == trace -> - ct:timetrap({seconds, 20}), + ct:timetrap({seconds, 40}), Prop = proplists:get_value(tc_group_properties, Config), Name = proplists:get_value(name, Prop), Cb = case Name of @@ -618,6 +624,51 @@ ipv6(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- +chunked_post() -> + [{doc,"Test option max_client_body_chunk"}]. +chunked_post(Config) when is_list(Config) -> + ok = http_status("POST /cgi-bin/erl/httpd_example:post_chunked ", + {"Content-Length:833 \r\n", + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" + "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"}, + [{http_version, "HTTP/1.1"} |Config], + [{statuscode, 200}]), + ok = http_status("POST /cgi-bin/erl/httpd_example:post_chunked ", + {"Content-Length:2 \r\n", + "ZZ" + }, + [{http_version, "HTTP/1.1"} |Config], + [{statuscode, 200}]). + +chunked_chunked_encoded_post() -> + [{doc,"Test option max_client_body_chunk with chunked client encoding"}]. +chunked_chunked_encoded_post(Config) when is_list(Config) -> + Chunk = http_chunk:encode("ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"), + LastChunk = http_chunk:encode_last(), + Chunks = lists:duplicate(10000, Chunk), + ok = http_status("POST /cgi-bin/erl/httpd_example:post_chunked ", + {"Transfer-Encoding:chunked \r\n", + [Chunks | LastChunk]}, + [{http_version, "HTTP/1.1"} | Config], + [{statuscode, 200}]). + + +%%------------------------------------------------------------------------- htaccess_1_1(Config) when is_list(Config) -> htaccess([{http_version, "HTTP/1.1"} | Config]). @@ -1568,6 +1619,7 @@ start_apps(Group) when Group == http_basic; Group == http_htaccess; Group == http_security; Group == http_reload; + Group == http_post; Group == http_mime_types-> inets_test_lib:start_apps([inets]). @@ -1614,6 +1666,8 @@ server_config(https_basic, Config) -> basic_conf() ++ server_config(https, Config); server_config(http_reload, Config) -> [{keep_alive_timeout, 2}] ++ server_config(http, Config); +server_config(http_post, Config) -> + [{max_client_body_chunk, 10}] ++ server_config(http, Config); server_config(https_reload, Config) -> [{keep_alive_timeout, 2}] ++ server_config(https, Config); server_config(http_limit, Config) -> diff --git a/lib/kernel/doc/src/disk_log.xml b/lib/kernel/doc/src/disk_log.xml index 1be28adfb8..884cb32c0c 100644 --- a/lib/kernel/doc/src/disk_log.xml +++ b/lib/kernel/doc/src/disk_log.xml @@ -972,7 +972,7 @@ <item> <p>Specifies if messages will be sent to <c>error_logger</c> on recoverable errors with - the log files. Defaults to <c>true</c>.</p> + the log files. Defaults to <c>false</c>.</p> </item> </taglist> <p><c>open/1</c> returns <c>{ok, <anno>Log</anno>}</c> if the diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index e1cf45109d..9cd03ffcad 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,21 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 5.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>The documentation for the 'quiet' option in + disk_log:open/1 had an incorrect default value.</p> + <p> + Own Id: OTP-14498</p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 5.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src index 77085b2064..f1ef70a373 100644 --- a/lib/kernel/src/kernel.appup.src +++ b/lib/kernel/src/kernel.appup.src @@ -18,7 +18,7 @@ %% %CopyrightEnd% {"%VSN%", %% Up from - max one major revision back - [{<<"5\\.[0-2](\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-19.* + [{<<"5\\.[0-3](\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-19.*, OTP-20.0 %% Down to - max one major revision back - [{<<"5\\.[0-2](\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-19.* + [{<<"5\\.[0-3](\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-19.*, OTP-20.0 }. diff --git a/lib/kernel/test/gen_tcp_misc_SUITE.erl b/lib/kernel/test/gen_tcp_misc_SUITE.erl index 929f66d400..331864b5de 100644 --- a/lib/kernel/test/gen_tcp_misc_SUITE.erl +++ b/lib/kernel/test/gen_tcp_misc_SUITE.erl @@ -50,9 +50,8 @@ killing_acceptor/1,killing_multi_acceptors/1,killing_multi_acceptors2/1, several_accepts_in_one_go/1, accept_system_limit/1, 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_7731/1, zombie_sockets/1, otp_7816/1, otp_8102/1, + wrapping_oct/0, wrapping_oct/1, otp_9389/1, otp_13939/1]). %% Internal exports. -export([sender/3, not_owner/1, passive_sockets_server/2, priority_server/1, @@ -3014,3 +3013,42 @@ ok({ok,V}) -> V. get_hostname(Name) -> "@"++Host = lists:dropwhile(fun(C) -> C =/= $@ end, atom_to_list(Name)), Host. + +otp_13939(doc) -> + ["Check that writing to a remotely closed socket doesn't block forever " + "when exit_on_close is false."]; +otp_13939(suite) -> + []; +otp_13939(Config) when is_list(Config) -> + {Pid, Ref} = spawn_opt( + fun() -> + {ok, Listener} = gen_tcp:listen(0, [{exit_on_close, false}]), + {ok, Port} = inet:port(Listener), + + spawn_link( + fun() -> + {ok, Client} = gen_tcp:connect("localhost", Port, + [{active, false}]), + ok = gen_tcp:close(Client) + end), + + {ok, Accepted} = gen_tcp:accept(Listener), + + ok = gen_tcp:send(Accepted, <<0:(10*1024*1024*8)>>), + + %% The bug surfaces when there's a delay between the send + %% operations; inet:getstat is a red herring. + timer:sleep(100), + + {error, Code} = gen_tcp:send(Accepted, <<0:(10*1024*1024*8)>>), + ct:pal("gen_tcp:send returned ~p~n", [Code]) + end, [link, monitor]), + + receive + {'DOWN', Ref, process, Pid, normal} -> + ok + after 1000 -> + demonitor(Ref, [flush]), + exit(Pid, normal), + ct:fail("Server process blocked on send.") + end. diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index 4edecd8969..c9463241d1 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 5.3 +KERNEL_VSN = 5.3.1 diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml index 2bfc174cae..d50994306b 100644 --- a/lib/runtime_tools/doc/src/notes.xml +++ b/lib/runtime_tools/doc/src/notes.xml @@ -32,6 +32,22 @@ <p>This document describes the changes made to the Runtime_Tools application.</p> +<section><title>Runtime_Tools 1.12.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A faulty encoding comment was added when saving trace + patterns to file. This is now corrected.</p> + <p> + Own Id: OTP-14479</p> + </item> + </list> + </section> + +</section> + <section><title>Runtime_Tools 1.12</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/runtime_tools/src/dbg.erl b/lib/runtime_tools/src/dbg.erl index e82f27896d..92938ed5c1 100644 --- a/lib/runtime_tools/src/dbg.erl +++ b/lib/runtime_tools/src/dbg.erl @@ -268,7 +268,7 @@ wtp(FileName) -> {error, Reason} -> {error, Reason}; {ok, File} -> - io:put_chars(File, "%% coding: utf8\n"), + io:format(File, "%% ~s\n", [epp:encoding_to_string(utf8)]), pt_doforall(fun ({_, Val}, _) when is_list(Val) -> io:format(File, "~tp.~n", [Val]); ({_, _}, _) -> diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk index 5ee39a25fe..7296221033 100644 --- a/lib/runtime_tools/vsn.mk +++ b/lib/runtime_tools/vsn.mk @@ -1 +1 @@ -RUNTIME_TOOLS_VSN = 1.12 +RUNTIME_TOOLS_VSN = 1.12.1 diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index f93753f1d2..5826d14a4a 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,22 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.5.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + All unknown options are sent to the transport handler + regardless of type.</p> + <p> + Own Id: OTP-14541 Aux Id: EIRERL-63 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.5</title> <section><title>Improvements and New Features</title> diff --git a/lib/ssh/src/ssh_options.erl b/lib/ssh/src/ssh_options.erl index 7eeed70739..b41ad8b33b 100644 --- a/lib/ssh/src/ssh_options.erl +++ b/lib/ssh/src/ssh_options.erl @@ -236,7 +236,10 @@ save({Key,Value}, Defs, OptMap) when is_map(OptMap) -> %% by the check fun will give an error exception: error:{check,{BadValue,Extra}} -> error({eoptions, {Key,BadValue}, Extra}) - end. + end; +save(Opt, _Defs, OptMap) when is_map(OptMap) -> + OptMap#{socket_options := [Opt | maps:get(socket_options,OptMap)]}. + %%%================================================================ %%% diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index 7208baca6e..006228f8e7 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.5 +SSH_VSN = 4.5.1 APP_VSN = "ssh-$(SSH_VSN)" diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index 46454e9b80..bdd5b39cd3 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -31,6 +31,38 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 3.4.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> A bug in <c>proc_lib:format()</c> introduced in + Erlang/OTP 20.0 is corrected. </p> + <p> + Own Id: OTP-14482 Aux Id: PR-1488 </p> + </item> + <item> + <p> + Fix string:len/1 to be compatible with previous versions.</p> + <p> + Own Id: OTP-14487 Aux Id: ERIERL-40 </p> + </item> + <item> + <p> + In OTP-20.0, the behavior of c, make, and ct_make was + changed so that in some cases the beam files by default + would be written to the directory where the source files + were found. This is now changed back to the old behavior + so beam files are by default written to current + directory.</p> + <p> + Own Id: OTP-14489 Aux Id: ERL-438 </p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl index 4ab9234b81..c04a201ce1 100644 --- a/lib/stdlib/src/c.erl +++ b/lib/stdlib/src/c.erl @@ -255,7 +255,7 @@ safe_recompile(File, Options, BeamFile) -> compile_and_load(File, Opts0) when is_list(Opts0) -> Opts = [report_errors, report_warnings | ensure_from(filename:extension(File), - ensure_outdir(filename:dirname(File), Opts0))], + ensure_outdir(".", Opts0))], case compile:file(File, Opts) of {ok,Mod} -> %Listing file. purge_and_load(Mod, File, Opts); diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl index 9ce8e7d60e..d4d1bdccec 100644 --- a/lib/stdlib/src/proc_lib.erl +++ b/lib/stdlib/src/proc_lib.erl @@ -779,11 +779,13 @@ format_link_report([Link|Reps], Indent, Extra) -> LinkIndent = [" ",Indent], [Indent,"neighbour:\n",format_report(Rep, LinkIndent, Extra)| format_link_report(Reps, Indent, Extra)]; -format_link_report([], _, _) -> - []. +format_link_report(Rep, Indent, Extra) -> + format_report(Rep, Indent, Extra). format_report(Rep, Indent, Extra) when is_list(Rep) -> format_rep(Rep, Indent, Extra); +format_report(Rep, Indent, {Enc,unlimited}) -> + io_lib:format("~s~"++modifier(Enc)++"p~n", [Indent, Rep]); format_report(Rep, Indent, {Enc,Depth}) -> io_lib:format("~s~"++modifier(Enc)++"P~n", [Indent, Rep, Depth]). diff --git a/lib/stdlib/src/string.erl b/lib/stdlib/src/string.erl index 6f7009b5d9..4972da297d 100644 --- a/lib/stdlib/src/string.erl +++ b/lib/stdlib/src/string.erl @@ -384,7 +384,7 @@ to_float(String) -> end. to_number(String, Number, Rest, List, _Tail) when is_binary(String) -> - BSz = length(List)-length(Rest), + BSz = erlang:length(List)-erlang:length(Rest), <<_:BSz/binary, Cont/binary>> = String, {Number, Cont}; to_number(_, Number, Rest, _, Tail) -> @@ -1344,7 +1344,7 @@ bin_search_str(Bin0, Start, Cont, [CP|_]=SearchCPs) -> String :: string(), Length :: non_neg_integer(). -len(S) -> length(S). +len(S) -> erlang:length(S). %% equal(String1, String2) %% Test if 2 strings are equal. @@ -1689,7 +1689,7 @@ left(String, Len) when is_integer(Len) -> left(String, Len, $\s). Character :: char(). left(String, Len, Char) when is_integer(Char) -> - Slen = length(String), + Slen = erlang:length(String), if Slen > Len -> substr(String, 1, Len); Slen < Len -> l_pad(String, Len-Slen, Char); @@ -1714,7 +1714,7 @@ right(String, Len) when is_integer(Len) -> right(String, Len, $\s). Character :: char(). right(String, Len, Char) when is_integer(Char) -> - Slen = length(String), + Slen = erlang:length(String), if Slen > Len -> substr(String, Slen-Len+1); Slen < Len -> r_pad(String, Len-Slen, Char); @@ -1741,7 +1741,7 @@ centre(String, Len) when is_integer(Len) -> centre(String, Len, $\s). centre(String, 0, Char) when is_list(String), is_integer(Char) -> []; % Strange cases to centre string centre(String, Len, Char) when is_integer(Char) -> - Slen = length(String), + Slen = erlang:length(String), if Slen > Len -> substr(String, (Slen-Len) div 2 + 1, Len); Slen < Len -> diff --git a/lib/stdlib/test/c_SUITE.erl b/lib/stdlib/test/c_SUITE.erl index 4bd32a30f8..f01988478c 100644 --- a/lib/stdlib/test/c_SUITE.erl +++ b/lib/stdlib/test/c_SUITE.erl @@ -21,7 +21,9 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2]). -export([c_1/1, c_2/1, c_3/1, c_4/1, nc_1/1, nc_2/1, nc_3/1, nc_4/1, - ls/1, memory/1]). + c_default_outdir_1/1, c_default_outdir_2/1, + nc_default_outdir_1/1, nc_default_outdir_2/1, + ls/1, memory/1]). -include_lib("common_test/include/ct.hrl"). @@ -30,7 +32,10 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [c_1, c_2, c_3, c_4, nc_1, nc_2, nc_3, nc_4, ls, memory]. + [c_1, c_2, c_3, c_4, nc_1, nc_2, nc_3, nc_4, + c_default_outdir_1, c_default_outdir_2, + nc_default_outdir_1, nc_default_outdir_2, + ls, memory]. groups() -> []. @@ -124,6 +129,50 @@ nc_4(Config) when is_list(Config) -> Result = nc(R,[{outdir,W}]), {ok, m} = Result. +c_default_outdir_1(Config) -> + R = filename:join(proplists:get_value(data_dir, Config), "m.erl"), + W = proplists:get_value(priv_dir, Config), + file:set_cwd(W), + Obj = "m" ++ code:objfile_extension(), + _ = file:delete(Obj), + false = filelib:is_file(Obj), + Result = c:c(R), + {ok, m} = Result, + true = filelib:is_file(Obj). + +c_default_outdir_2(Config) -> + R = filename:join(proplists:get_value(data_dir, Config), "m"), + W = proplists:get_value(priv_dir, Config), + file:set_cwd(W), + Obj = "m" ++ code:objfile_extension(), + _ = file:delete(Obj), + false = filelib:is_file(Obj), + Result = c:c(R), + {ok, m} = Result, + true = filelib:is_file(Obj). + +nc_default_outdir_1(Config) -> + R = filename:join(proplists:get_value(data_dir, Config), "m.erl"), + W = proplists:get_value(priv_dir, Config), + file:set_cwd(W), + Obj = "m" ++ code:objfile_extension(), + _ = file:delete(Obj), + false = filelib:is_file(Obj), + Result = c:nc(R), + {ok, m} = Result, + true = filelib:is_file(Obj). + +nc_default_outdir_2(Config) -> + R = filename:join(proplists:get_value(data_dir, Config), "m"), + W = proplists:get_value(priv_dir, Config), + file:set_cwd(W), + Obj = "m" ++ code:objfile_extension(), + _ = file:delete(Obj), + false = filelib:is_file(Obj), + Result = c:nc(R), + {ok, m} = Result, + true = filelib:is_file(Obj). + ls(Config) when is_list(Config) -> Directory = proplists:get_value(data_dir, Config), ok = c:ls(Directory), diff --git a/lib/stdlib/test/proc_lib_SUITE.erl b/lib/stdlib/test/proc_lib_SUITE.erl index 029e6286e4..c4fafe82a4 100644 --- a/lib/stdlib/test/proc_lib_SUITE.erl +++ b/lib/stdlib/test/proc_lib_SUITE.erl @@ -28,7 +28,7 @@ init_per_group/2,end_per_group/2, crash/1, stacktrace/1, sync_start_nolink/1, sync_start_link/1, spawn_opt/1, sp1/0, sp2/0, sp3/1, sp4/2, sp5/1, '\x{447}'/0, - hibernate/1, stop/1, t_format/1]). + hibernate/1, stop/1, t_format/1, t_format_arbitrary/1]). -export([ otp_6345/1, init_dont_hang/1]). -export([hib_loop/1, awaken/1]). @@ -51,7 +51,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [crash, stacktrace, {group, sync_start}, spawn_opt, hibernate, - {group, tickets}, stop, t_format]. + {group, tickets}, stop, t_format, t_format_arbitrary]. groups() -> [{tickets, [], [otp_6345, init_dont_hang]}, @@ -78,6 +78,14 @@ end_per_group(_GroupName, Config) -> %% synchronous, and we want to test that the crash report is ok. %%----------------------------------------------------------------- crash(Config) when is_list(Config) -> + ok = application:unset_env(kernel, error_logger_format_depth), + crash_1(Config), + ok = application:set_env(kernel, error_logger_format_depth, 30), + crash_1(Config), + ok = application:unset_env(kernel, error_logger_format_depth), + ok. + +crash_1(_Config) -> error_logger:add_report_handler(?MODULE, self()), %% Make sure that we don't get a crash report if a process @@ -562,9 +570,32 @@ t_format() -> ok. +t_format_arbitrary(_Config) -> + error_logger:tty(false), + try + t_format_arbitrary() + after + error_logger:tty(true) + end, + ok. + +t_format_arbitrary() -> + A = list_to_atom([1024]), + do_test_format([fake_report, A], unlimited), + do_test_format([fake_report, A], 20), + + do_test_format([fake_report, foo], unlimited), + do_test_format([fake_report, foo], 20), + do_test_format([fake_report, []], unlimited), + do_test_format([fake_report, []], 20). + do_test_format(Report, Depth) -> - io:format("*** Depth = ~p", [Depth]), - S0 = proc_lib:format(Report, latin1, Depth), + do_test_format(Report, latin1, Depth), + do_test_format(Report, unicode, Depth). + +do_test_format(Report, Encoding, Depth) -> + io:format("*** Depth = ~p, Encoding = ~p", [Depth, Encoding]), + S0 = proc_lib:format(Report, Encoding, Depth), S = lists:flatten(S0), io:put_chars(S), length(S). @@ -584,7 +615,7 @@ init(Tester) -> {ok, Tester}. handle_event({error_report, _GL, {Pid, crash_report, Report}}, Tester) -> - io:format("~s\n", [proc_lib:format(Report)]), + io:format("~ts\n", [proc_lib:format(Report)]), Tester ! {crash_report, Pid, Report}, {ok, Tester}; handle_event(_Event, State) -> diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index f062c7fe6e..8a83cdec1e 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.4 +STDLIB_VSN = 3.4.1 diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index bdd5455354..f0df43bf2b 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -31,6 +31,26 @@ </header> <p>This document describes the changes made to the Tools application.</p> +<section><title>Tools 2.10.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + In OTP-20.0, the behavior of c, make, and ct_make was + changed so that in some cases the beam files by default + would be written to the directory where the source files + were found. This is now changed back to the old behavior + so beam files are by default written to current + directory.</p> + <p> + Own Id: OTP-14489 Aux Id: ERL-438 </p> + </item> + </list> + </section> + +</section> + <section><title>Tools 2.10</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/tools/src/make.erl b/lib/tools/src/make.erl index ce30156db6..6554d338af 100644 --- a/lib/tools/src/make.erl +++ b/lib/tools/src/make.erl @@ -267,15 +267,47 @@ include_opt([]) -> recompile(File, true, _Load, _Opts) -> io:format("Out of date: ~ts\n",[File]); -recompile(File, false, noload, Opts) -> +recompile(File, false, Load, Opts) -> io:format("Recompile: ~ts\n",[File]), - compile:file(File, [report_errors, report_warnings, error_summary |Opts]); -recompile(File, false, load, Opts) -> - io:format("Recompile: ~ts\n",[File]), - c:c(File, Opts); -recompile(File, false, netload, Opts) -> - io:format("Recompile: ~ts\n",[File]), - c:nc(File, Opts). + case compile:file(File, [report_errors, report_warnings |Opts]) of + Ok when is_tuple(Ok), element(1,Ok)==ok -> + maybe_load(element(2,Ok), Load, Opts); + _Error -> + error + end. + +maybe_load(_Mod, noload, _Opts) -> + ok; +maybe_load(Mod, Load, Opts) -> + %% We have compiled File with options Opts. Find out where the + %% output file went to, and load it. + case compile:output_generated(Opts) of + true -> + Dir = proplists:get_value(outdir,Opts,"."), + do_load(Dir, Mod, Load); + false -> + io:format("** Warning: No object file created - nothing loaded **~n"), + ok + end. + +do_load(Dir, Mod, load) -> + code:purge(Mod), + case code:load_abs(filename:join(Dir, Mod),Mod) of + {module,Mod} -> + {ok,Mod}; + Other -> + Other + end; +do_load(Dir, Mod, netload) -> + Obj = atom_to_list(Mod) ++ code:objfile_extension(), + Fname = filename:join(Dir, Obj), + case file:read_file(Fname) of + {ok,Bin} -> + rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]), + {ok,Mod}; + Other -> + Other + end. exists(File) -> case file:read_file_info(File) of diff --git a/lib/tools/test/make_SUITE.erl b/lib/tools/test/make_SUITE.erl index 2f6fe1c732..02da4f4ace 100644 --- a/lib/tools/test/make_SUITE.erl +++ b/lib/tools/test/make_SUITE.erl @@ -36,7 +36,7 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [make_all, make_files, recompile_on_changed_include, + [make_all, make_files, load, netload, recompile_on_changed_include, emake_opts, {group, otp_6057}]. groups() -> @@ -55,6 +55,21 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> otp_6057_end(Config). +init_per_testcase(_,Config) -> + Config. + +end_per_testcase(netload,_Config) -> + %% Stop slave - in case of failure + Nodes = nodes(), + case [N || N <- Nodes, + "make_SUITE_netload" == hd(string:lexemes(atom_to_list(N),"@"))] of + [Node] -> + ct_slave:stop(Node); + _ -> + ok + end; +end_per_testcase(_,_Config) -> + ok. test_files() -> ["test1", "test2", "test3", "test4"]. @@ -83,6 +98,32 @@ make_files(Config) when is_list(Config) -> ensure_no_messages(), ok. +load(Config) -> + Current = prepare_data_dir(Config), + code:purge(test1), + code:delete(test1), + false = code:is_loaded(test1), + up_to_date = make:files([test1], [load]), + {file,_} = code:is_loaded(test1), + file:set_cwd(Current), + ensure_no_messages(), + ok. + +netload(Config) -> + Current = prepare_data_dir(Config), + code:purge(test1), + code:delete(test1), + false = code:is_loaded(test1), + {ok,Node} = ct_slave:start(make_SUITE_netload), + up_to_date = make:files([test1], [netload]), + timer:sleep(1000), % async, so give some time + {file,F} = code:is_loaded(test1), + {file,F} = rpc:call(Node,code,is_loaded,[test1]), + ct_slave:stop(Node), + file:set_cwd(Current), + ensure_no_messages(), + ok. + recompile_on_changed_include(Config) -> Current = prepare_data_dir(Config), diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index 8aa7814e1d..831d850217 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 2.10 +TOOLS_VSN = 2.10.1 |