diff options
Diffstat (limited to 'erts/preloaded')
38 files changed, 2907 insertions, 2464 deletions
diff --git a/erts/preloaded/ebin/atomics.beam b/erts/preloaded/ebin/atomics.beam Binary files differnew file mode 100644 index 0000000000..a5ac24f0b8 --- /dev/null +++ b/erts/preloaded/ebin/atomics.beam diff --git a/erts/preloaded/ebin/counters.beam b/erts/preloaded/ebin/counters.beam Binary files differnew file mode 100644 index 0000000000..a1aa34a415 --- /dev/null +++ b/erts/preloaded/ebin/counters.beam diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam Binary files differindex 796cbd74c5..37903d24b6 100644 --- a/erts/preloaded/ebin/erl_prim_loader.beam +++ b/erts/preloaded/ebin/erl_prim_loader.beam diff --git a/erts/preloaded/ebin/erl_tracer.beam b/erts/preloaded/ebin/erl_tracer.beam Binary files differindex 4406a82a36..2509f238bf 100644 --- a/erts/preloaded/ebin/erl_tracer.beam +++ b/erts/preloaded/ebin/erl_tracer.beam diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex c68debeabc..7563663807 100644 --- a/erts/preloaded/ebin/erlang.beam +++ b/erts/preloaded/ebin/erlang.beam diff --git a/erts/preloaded/ebin/erts_code_purger.beam b/erts/preloaded/ebin/erts_code_purger.beam Binary files differindex 6956eee740..bc697d11d7 100644 --- a/erts/preloaded/ebin/erts_code_purger.beam +++ b/erts/preloaded/ebin/erts_code_purger.beam diff --git a/erts/preloaded/ebin/erts_dirty_process_code_checker.beam b/erts/preloaded/ebin/erts_dirty_process_code_checker.beam Binary files differdeleted file mode 100644 index a7ac116c05..0000000000 --- a/erts/preloaded/ebin/erts_dirty_process_code_checker.beam +++ /dev/null diff --git a/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam Binary files differnew file mode 100644 index 0000000000..5b788368af --- /dev/null +++ b/erts/preloaded/ebin/erts_dirty_process_signal_handler.beam diff --git a/erts/preloaded/ebin/erts_internal.beam b/erts/preloaded/ebin/erts_internal.beam Binary files differindex 227b62b7d3..b8415a9833 100644 --- a/erts/preloaded/ebin/erts_internal.beam +++ b/erts/preloaded/ebin/erts_internal.beam diff --git a/erts/preloaded/ebin/erts_literal_area_collector.beam b/erts/preloaded/ebin/erts_literal_area_collector.beam Binary files differindex 71f3c2ec8c..e2a8c65f38 100644 --- a/erts/preloaded/ebin/erts_literal_area_collector.beam +++ b/erts/preloaded/ebin/erts_literal_area_collector.beam diff --git a/erts/preloaded/ebin/init.beam b/erts/preloaded/ebin/init.beam Binary files differindex 849273f746..fee2da33a6 100644 --- a/erts/preloaded/ebin/init.beam +++ b/erts/preloaded/ebin/init.beam diff --git a/erts/preloaded/ebin/otp_ring0.beam b/erts/preloaded/ebin/otp_ring0.beam Binary files differindex b601c048b3..324e111ad1 100644 --- a/erts/preloaded/ebin/otp_ring0.beam +++ b/erts/preloaded/ebin/otp_ring0.beam diff --git a/erts/preloaded/ebin/persistent_term.beam b/erts/preloaded/ebin/persistent_term.beam Binary files differnew file mode 100644 index 0000000000..c73da80a98 --- /dev/null +++ b/erts/preloaded/ebin/persistent_term.beam diff --git a/erts/preloaded/ebin/prim_buffer.beam b/erts/preloaded/ebin/prim_buffer.beam Binary files differnew file mode 100644 index 0000000000..75e5b7c9cb --- /dev/null +++ b/erts/preloaded/ebin/prim_buffer.beam diff --git a/erts/preloaded/ebin/prim_eval.beam b/erts/preloaded/ebin/prim_eval.beam Binary files differindex 77909b01f0..ddda4764e1 100644 --- a/erts/preloaded/ebin/prim_eval.beam +++ b/erts/preloaded/ebin/prim_eval.beam diff --git a/erts/preloaded/ebin/prim_file.beam b/erts/preloaded/ebin/prim_file.beam Binary files differindex 5bbbaf14d5..2d1ce7d631 100644 --- a/erts/preloaded/ebin/prim_file.beam +++ b/erts/preloaded/ebin/prim_file.beam diff --git a/erts/preloaded/ebin/prim_inet.beam b/erts/preloaded/ebin/prim_inet.beam Binary files differindex 1a573ce297..558968b58a 100644 --- a/erts/preloaded/ebin/prim_inet.beam +++ b/erts/preloaded/ebin/prim_inet.beam diff --git a/erts/preloaded/ebin/prim_zip.beam b/erts/preloaded/ebin/prim_zip.beam Binary files differindex 6afeb454d6..51721a27a8 100644 --- a/erts/preloaded/ebin/prim_zip.beam +++ b/erts/preloaded/ebin/prim_zip.beam diff --git a/erts/preloaded/ebin/zlib.beam b/erts/preloaded/ebin/zlib.beam Binary files differindex 4c48742344..4519b540c4 100644 --- a/erts/preloaded/ebin/zlib.beam +++ b/erts/preloaded/ebin/zlib.beam diff --git a/erts/preloaded/src/Makefile b/erts/preloaded/src/Makefile index 2ab9edaf5e..e1bd5bc295 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -1,7 +1,7 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2008-2016. All Rights Reserved. +# Copyright Ericsson AB 2008-2018. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -36,6 +36,7 @@ include $(ERL_TOP)/lib/kernel/vsn.mk PRE_LOADED_ERL_MODULES = \ erl_prim_loader \ init \ + prim_buffer \ prim_file \ prim_inet \ zlib \ @@ -46,7 +47,10 @@ PRE_LOADED_ERL_MODULES = \ erts_internal \ erl_tracer \ erts_literal_area_collector \ - erts_dirty_process_code_checker + erts_dirty_process_signal_handler \ + atomics \ + counters \ + persistent_term PRE_LOADED_BEAM_MODULES = \ prim_eval @@ -73,7 +77,7 @@ KERNEL_SRC=$(ERL_TOP)/lib/kernel/src KERNEL_INCLUDE=$(ERL_TOP)/lib/kernel/include STDLIB_INCLUDE=$(ERL_TOP)/lib/stdlib/include -ERL_COMPILE_FLAGS += +warn_obsolete_guard +debug_info -I$(KERNEL_SRC) -I$(KERNEL_INCLUDE) +ERL_COMPILE_FLAGS += +debug_info -I$(KERNEL_SRC) -I$(KERNEL_INCLUDE) debug opt: $(TARGET_FILES) @@ -116,7 +120,7 @@ prim_eval.beam: prim_eval.S prim_eval.abstr # Include dependencies -- list below added by PaN $(EBIN)/erl_prim_loader.beam: $(KERNEL_SRC)/inet_boot.hrl $(KERNEL_INCLUDE)/file.hrl -$(EBIN)/prim_file.beam: $(KERNEL_INCLUDE)/file.hrl +$(EBIN)/prim_file.beam: $(KERNEL_SRC)/file_int.hrl $(KERNEL_INCLUDE)/file.hrl $(EBIN)/prim_inet.beam: $(KERNEL_SRC)/inet_int.hrl $(KERNEL_INCLUDE)/inet_sctp.hrl $(EBIN)/prim_zip.beam: zip_internal.hrl $(KERNEL_INCLUDE)/file.hrl $(STDLIB_INCLUDE)/zip.hrl $(EBIN)/init.erl: $(KERNEL_INCLUDE)/file.hrl diff --git a/erts/preloaded/src/add_abstract_code b/erts/preloaded/src/add_abstract_code index 943987872e..9040199417 100644 --- a/erts/preloaded/src/add_abstract_code +++ b/erts/preloaded/src/add_abstract_code @@ -4,7 +4,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-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. @@ -28,12 +28,12 @@ main([BeamFile,AbstrFile]) -> {ok,_,Chunks0} = beam_lib:all_chunks(BeamFile), {ok,Abstr} = file:consult(AbstrFile), - Chunks1 = lists:keyreplace("Abst", 1, Chunks0, - {"Abst",term_to_binary({raw_abstract_v1,Abstr})}), - {"CInf",CInf0} = lists:keyfind("CInf", 1, Chunks1), - CInf = fix_options(CInf0), - Chunks = lists:keyreplace("CInf", 1, Chunks1, {"CInf",CInf}), - {ok,Module} = beam_lib:build_module(Chunks), + {"CInf",CInf0} = lists:keyfind("CInf", 1, Chunks0), + {CInf, COpts} = fix_options(CInf0), + Chunks1 = lists:keyreplace("CInf", 1, Chunks0, {"CInf",CInf}), + Chunks2 = lists:keyreplace("Dbgi", 1, Chunks1, + {"Dbgi",term_to_binary({debug_info_v1,erl_abstract_code,{Abstr, COpts}})}), + {ok,Module} = beam_lib:build_module(Chunks2), ok = file:write_file(BeamFile, Module), init:stop(). @@ -42,4 +42,4 @@ fix_options(CInf0) -> {options,Opts0} = lists:keyfind(options, 1, CInf1), Opts = Opts0 -- [from_asm], CInf = lists:keyreplace(options, 1, CInf1, {options,Opts}), - term_to_binary(CInf). + {term_to_binary(CInf), Opts}. diff --git a/erts/preloaded/src/atomics.erl b/erts/preloaded/src/atomics.erl new file mode 100644 index 0000000000..d1fe5e65cf --- /dev/null +++ b/erts/preloaded/src/atomics.erl @@ -0,0 +1,119 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% Purpose : Main atomics API module. + +-module(atomics). + +-export([new/2, + put/3, get/2, + add/3, add_get/3, + sub/3, sub_get/3, + exchange/3, compare_exchange/4, + info/1]). + +-export_type([atomics_ref/0]). + +-opaque atomics_ref() :: reference(). + +-define(OPT_SIGNED, (1 bsl 0)). +-define(OPT_DEFAULT, ?OPT_SIGNED). + +-spec new(Arity, Opts) -> atomics_ref() when + Arity :: pos_integer(), + Opts :: [Opt], + Opt :: {signed, boolean()}. +new(Arity, Opts) -> + erts_internal:atomics_new(Arity, encode_opts(Opts, ?OPT_DEFAULT)). + +encode_opts([{signed, true}|T], Acc) -> + encode_opts(T, Acc bor ?OPT_SIGNED); +encode_opts([{signed, false}|T], Acc) -> + encode_opts(T, Acc band (bnot ?OPT_SIGNED)); +encode_opts([], Acc) -> + Acc; +encode_opts(_, _) -> + erlang:error(badarg). + +-spec put(Ref, Ix, Value) -> ok when + Ref :: atomics_ref(), + Ix :: integer(), + Value :: integer(). +put(_Ref, _Ix, _Value) -> + erlang:nif_error(undef). + +-spec get(Ref, Ix) -> integer() when + Ref :: atomics_ref(), + Ix :: integer(). +get(_Ref, _Ix) -> + erlang:nif_error(undef). + +-spec add(Ref, Ix, Incr) -> ok when + Ref :: atomics_ref(), + Ix :: integer(), + Incr :: integer(). +add(_Ref, _Ix, _Incr) -> + erlang:nif_error(undef). + +-spec add_get(Ref, Ix, Incr) -> integer() when + Ref :: atomics_ref(), + Ix :: integer(), + Incr :: integer(). +add_get(_Ref, _Ix, _Incr) -> + erlang:nif_error(undef). + +-spec sub(Ref, Ix, Decr) -> ok when + Ref :: atomics_ref(), + Ix :: integer(), + Decr :: integer(). +sub(Ref, Ix, Decr) -> + ?MODULE:add(Ref, Ix, -Decr). + +-spec sub_get(Ref, Ix, Decr) -> integer() when + Ref :: atomics_ref(), + Ix :: integer(), + Decr :: integer(). +sub_get(Ref, Ix, Decr) -> + ?MODULE:add_get(Ref, Ix, -Decr). + +-spec exchange(Ref, Ix, Desired) -> integer() when + Ref :: atomics_ref(), + Ix :: integer(), + Desired :: integer(). +exchange(_Ref, _Ix, _Desired) -> + erlang:nif_error(undef). + +-spec compare_exchange(Ref, Ix, Expected, Desired) -> ok | integer() when + Ref :: atomics_ref(), + Ix :: integer(), + Expected :: integer(), + Desired :: integer(). +compare_exchange(_Ref, _Ix, _Expected, _Desired) -> + erlang:nif_error(undef). + +-spec info(Ref) -> Info when + Ref :: atomics_ref(), + Info :: #{'size':=Size,'max':=Max,'min':=Min,'memory':=Memory}, + Size :: non_neg_integer(), + Max :: integer(), + Min :: integer(), + Memory :: non_neg_integer(). +info(_Ref) -> + erlang:nif_error(undef). diff --git a/erts/preloaded/src/counters.erl b/erts/preloaded/src/counters.erl new file mode 100644 index 0000000000..a0e3035e0f --- /dev/null +++ b/erts/preloaded/src/counters.erl @@ -0,0 +1,104 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% Purpose : Main atomics API module. + +-module(counters). + +-export([new/2, + get/2, + add/3, + sub/3, + put/3, + info/1]). + +-export_type([counters_ref/0]). + +-opaque counters_ref() :: {atomics, reference()} | {write_concurrency, reference()}. + +-spec new(Size, Opts) -> counters_ref() when + Size :: pos_integer(), + Opts :: [Opt], + Opt :: atomics | write_concurrency. +new(Size, [atomics]) -> + {atomics, atomics:new(Size, [{signed, true}])}; +new(Size, [write_concurrency]) -> + {write_concurrency, erts_internal:counters_new(Size)}; +new(Size, []) -> + new(Size, [atomics]); +new(_, _) -> + erlang:error(badarg). + +-spec get(Ref, Ix) -> integer() when + Ref :: counters_ref(), + Ix :: integer(). +get({atomics,Ref}, Ix) -> + atomics:get(Ref, Ix); +get({write_concurrency, Ref}, Ix) -> + erts_internal:counters_get(Ref, Ix); +get(_, _) -> + erlang:error(badarg). + + + +-spec add(Ref, Ix, Incr) -> ok when + Ref :: counters_ref(), + Ix :: integer(), + Incr :: integer(). +add({atomics, Ref}, Ix, Incr) -> + atomics:add(Ref, Ix, Incr); +add({write_concurrency, Ref}, Ix, Incr) -> + erts_internal:counters_add(Ref, Ix, Incr); +add(_, _, _) -> + erlang:error(badarg). + + +-spec sub(Ref, Ix, Decr) -> ok when + Ref :: counters_ref(), + Ix :: integer(), + Decr :: integer(). +sub(Ref, Ix, Decr) -> + add(Ref, Ix, -Decr). + + +-spec put(Ref, Ix, Value) -> ok when + Ref :: counters_ref(), + Ix :: integer(), + Value :: integer(). +put({atomics, Ref}, Ix, Value) -> + atomics:put(Ref, Ix, Value); +put({write_concurrency, Ref}, Ix, Value) -> + erts_internal:counters_put(Ref, Ix, Value); +put(_, _, _) -> + erlang:error(badarg). + + +-spec info(Ref) -> Info when + Ref :: counters_ref(), + Info :: #{'size':=Size, 'memory':=Memory}, + Size :: non_neg_integer(), + Memory :: non_neg_integer(). +info({atomics, Ref}) -> + atomics:info(Ref); +info({write_concurrency, Ref}) -> + erts_internal:counters_info(Ref); +info(_) -> + erlang:error(badarg). + diff --git a/erts/preloaded/src/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl index b3ec73a60e..ae5f86e017 100644 --- a/erts/preloaded/src/erl_prim_loader.erl +++ b/erts/preloaded/src/erl_prim_loader.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ -export([purge_archive_cache/0]). %% Used by init and the code server. --export([get_modules/2,get_modules/3]). +-export([get_modules/2,get_modules/3, is_basename/1]). -include_lib("kernel/include/file.hrl"). @@ -151,9 +151,8 @@ start_inet(Parent) -> loop(State, Parent, []). start_efile(Parent) -> - {ok, Port} = prim_file:start(), %% Check that we started in a valid directory. - case prim_file:get_cwd(Port) of + case prim_file:get_cwd() of {error, _} -> %% At this point in the startup, we have no error_logger at all. Report = "Invalid current directory or invalid filename " @@ -165,7 +164,7 @@ start_efile(Parent) -> end, PS = prim_init(), State = #state {loader = efile, - data = Port, + data = noport, timeout = ?EFILE_IDLE_TIMEOUT, prim_state = PS}, loop(State, Parent, []). @@ -300,8 +299,12 @@ check_file_result(Func, Target, {error,Reason}) -> end, %% this is equal to calling error_logger:error_report/1 which %% we don't want to do from code_server during system boot - error_logger ! {notify,{error_report,group_leader(), - {self(),std_error,Report}}}, + logger ! {log,error,#{label=>{?MODULE,file_error},report=>Report}, + #{pid=>self(), + gl=>group_leader(), + time=>erlang:monotonic_time(microsecond), + error_logger=>#{tag=>error_report, + type=>std_error}}}, error end; check_file_result(_, _, Other) -> @@ -401,12 +404,12 @@ handle_get_cwd(State = #state{loader = inet}, Drive) -> ?SAFE2(inet_get_cwd(State, Drive), State). handle_stop(State = #state{loader = efile}) -> - efile_stop_port(State); + State; handle_stop(State = #state{loader = inet}) -> inet_stop_port(State). -handle_exit(State = #state{loader = efile}, Who, Reason) -> - efile_exit_port(State, Who, Reason); +handle_exit(State = #state{loader = efile}, _Who, _Reason) -> + State; handle_exit(State = #state{loader = inet}, Who, Reason) -> inet_exit_port(State, Who, Reason). @@ -475,15 +478,6 @@ efile_get_cwd(#state{prim_state = PS} = State, Drive) -> {Res, PS2} = prim_get_cwd(PS, Drive), {Res, State#state{prim_state = PS2}}. -efile_stop_port(#state{data=Port}=State) -> - prim_file:close(Port), - State#state{data=noport}. - -efile_exit_port(State, Port, Reason) when State#state.data =:= Port -> - exit({port_died,Reason}); -efile_exit_port(State, _Port, _Reason) -> - State. - efile_timeout_handler(State, _Parent) -> prim_purge_cache(), State. @@ -555,17 +549,18 @@ efile_gm_get(Paths, Mod, ParentRef, Process) -> efile_gm_get_1([P|Ps], File0, Mod, {Parent,Ref}=PR, Process) -> File = join(P, File0), - Res = try prim_file:read_file(File) of - {ok,Bin} -> - gm_process(Mod, File, Bin, Process); - Error -> - _ = check_file_result(get_modules, File, Error), - efile_gm_get_1(Ps, File0, Mod, PR, Process) - catch - _:Reason -> - {error,{crash,Reason}} - end, - Parent ! {Ref,Mod,Res}; + try prim_file:read_file(File) of + {ok,Bin} -> + Res = gm_process(Mod, File, Bin, Process), + Parent ! {Ref,Mod,Res}; + Error -> + _ = check_file_result(get_modules, File, Error), + efile_gm_get_1(Ps, File0, Mod, PR, Process) + catch + _:Reason -> + Res = {error,{crash,Reason}}, + Parent ! {Ref,Mod,Res} + end; efile_gm_get_1([], _, Mod, {Parent,Ref}, _Process) -> Parent ! {Ref,Mod,{error,enoent}}. diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 652a954807..1ed6b6b284 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -31,8 +31,7 @@ -export([localtime_to_universaltime/1]). -export([suspend_process/1]). -export([min/2, max/2]). --export([dlink/1, dunlink/1, dsend/2, dsend/3, dgroup_leader/2, - dexit/2, dmonitor_node/3, dmonitor_p/2]). +-export([dmonitor_node/3]). -export([delay_trap/2]). -export([set_cookie/2, get_cookie/0]). -export([nodes/0]). @@ -40,15 +39,18 @@ -export([integer_to_list/2]). -export([integer_to_binary/2]). -export([set_cpu_topology/1, format_cpu_topology/1]). --export([await_proc_exit/3]). -export([memory/0, memory/1]). -export([alloc_info/1, alloc_sizes/1]). --export([gather_sched_wall_time_result/1, - await_sched_wall_time_modifications/2, - gather_gc_info_result/1]). +-export([gather_gc_info_result/1]). --deprecated([hash/2, now/0]). +-export([dist_ctrl_input_handler/2, + dist_ctrl_put_data/2, + dist_ctrl_get_data/1, + dist_ctrl_get_data_notification/1, + dist_get_stat/1]). + +-deprecated([get_stacktrace/0,now/0]). %% Get rid of autoimports of spawn to avoid clashes with ourselves. -compile({no_auto_import,[spawn_link/1]}). @@ -83,6 +85,18 @@ | 'micro_seconds' | 'nano_seconds'. +-opaque prepared_code() :: reference(). +-export_type([prepared_code/0]). + +-opaque nif_resource() :: reference(). +-export_type([nif_resource/0]). + +-opaque dist_handle() :: atom(). +-export_type([dist_handle/0]). + +-type iovec() :: [binary()]. +-export_type([iovec/0]). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Native code BIF stubs and their types %% (BIF's actually implemented in this module goes last in the file) @@ -100,33 +114,34 @@ -export([binary_to_list/3, binary_to_term/1, binary_to_term/2]). -export([bit_size/1, bitsize/1, bitstring_to_list/1]). -export([bump_reductions/1, byte_size/1, call_on_load_function/1]). --export([cancel_timer/1, cancel_timer/2, check_old_code/1, check_process_code/2, +-export([cancel_timer/1, cancel_timer/2, ceil/1, + check_old_code/1, check_process_code/2, check_process_code/3, crc32/1]). -export([crc32/2, crc32_combine/3, date/0, decode_packet/3]). -export([delete_element/2]). -export([delete_module/1, demonitor/1, demonitor/2, display/1]). --export([display_nl/0, display_string/1, dist_exit/3, erase/0, erase/1]). --export([error/1, error/2, exit/1, exit/2, external_size/1]). +-export([display_nl/0, display_string/1, erase/0, erase/1]). +-export([error/1, error/2, exit/1, exit/2, exit_signal/2, external_size/1]). -export([external_size/2, finish_after_on_load/2, finish_loading/1, float/1]). -export([float_to_binary/1, float_to_binary/2, - float_to_list/1, float_to_list/2]). + float_to_list/1, float_to_list/2, floor/1]). -export([fun_info/2, fun_info_mfa/1, fun_to_list/1, function_exported/3]). -export([garbage_collect/0, garbage_collect/1, garbage_collect/2]). -export([garbage_collect_message_area/0, get/0, get/1, get_keys/0, get_keys/1]). -export([get_module_info/1, get_stacktrace/0, group_leader/0]). -export([group_leader/2]). --export([halt/0, halt/1, halt/2, hash/2, +-export([halt/0, halt/1, halt/2, has_prepared_code_on_load/1, hibernate/3]). -export([insert_element/3]). -export([integer_to_binary/1, integer_to_list/1]). --export([iolist_size/1, iolist_to_binary/1]). --export([is_alive/0, is_builtin/3, is_process_alive/1, length/1, link/1]). +-export([iolist_size/1, iolist_to_binary/1, iolist_to_iovec/1]). +-export([is_alive/0, is_builtin/3, is_map_key/2, is_process_alive/1, length/1, link/1]). -export([list_to_atom/1, list_to_binary/1]). -export([list_to_bitstring/1, list_to_existing_atom/1, list_to_float/1]). -export([list_to_integer/1, list_to_integer/2]). --export([list_to_pid/1, list_to_tuple/1, loaded/0]). +-export([list_to_pid/1, list_to_port/1, list_to_ref/1, list_to_tuple/1, loaded/0]). -export([localtime/0, make_ref/0]). --export([map_size/1, match_spec_test/3, md5/1, md5_final/1]). +-export([map_size/1, map_get/2, match_spec_test/3, md5/1, md5_final/1]). -export([md5_init/0, md5_update/2, module_loaded/1, monitor/2]). -export([monitor_node/2, monitor_node/3, nif_error/1, nif_error/2]). -export([node/0, node/1, now/0, phash/2, phash2/1, phash2/2]). @@ -408,9 +423,11 @@ binary_to_term(_Binary) -> erlang:nif_error(undefined). %% binary_to_term/2 --spec binary_to_term(Binary, Opts) -> term() when +-spec binary_to_term(Binary, Opts) -> term() | {term(), Used} when Binary :: ext_binary(), - Opts :: [safe]. + Opt :: safe | used, + Opts :: [Opt], + Used :: pos_integer(). binary_to_term(_Binary, _Opts) -> erlang:nif_error(undefined). @@ -474,6 +491,13 @@ cancel_timer(_TimerRef) -> cancel_timer(_TimerRef, _Options) -> erlang:nif_error(undefined). +%% ceil/1 +%% Shadowed by erl_bif_types: erlang:ceil/1 +-spec ceil(Number) -> integer() when + Number :: number(). +ceil(_) -> + erlang:nif_error(undef). + %% check_old_code/1 -spec check_old_code(Module) -> boolean() when Module :: module(). @@ -676,14 +700,6 @@ display_nl() -> display_string(_P1) -> erlang:nif_error(undefined). -%% dist_exit/3 --spec erlang:dist_exit(P1, P2, P3) -> true when - P1 :: pid(), - P2 :: kill | noconnection | normal, - P3 :: pid() | port(). -dist_exit(_P1, _P2, _P3) -> - erlang:nif_error(undefined). - %% dt_append_vm_tag_data/1 -spec erlang:dt_append_vm_tag_data(IoData) -> IoDataRet when IoData :: iodata(), @@ -769,6 +785,13 @@ exit(_Reason) -> exit(_Pid, _Reason) -> erlang:nif_error(undefined). +%% exit_signal/2 +-spec erlang:exit_signal(Pid, Reason) -> true when + Pid :: pid() | port(), + Reason :: term(). +exit_signal(_Pid, _Reason) -> + erlang:nif_error(undefined). + %% external_size/1 -spec erlang:external_size(Term) -> non_neg_integer() when Term :: term(). @@ -783,9 +806,9 @@ external_size(_Term, _Options) -> erlang:nif_error(undefined). %% finish_loading/2 --spec erlang:finish_loading(PreparedCodeBinaries) -> ok | Error when - PreparedCodeBinaries :: [PreparedCodeBinary], - PreparedCodeBinary :: binary(), +-spec erlang:finish_loading(PreparedCodeList) -> ok | Error when + PreparedCodeList :: [PreparedCode], + PreparedCode :: prepared_code(), ModuleList :: [module()], Error :: {not_purged,ModuleList} | {on_load,ModuleList}. finish_loading(_List) -> @@ -837,6 +860,13 @@ float_to_list(_Float) -> float_to_list(_Float, _Options) -> erlang:nif_error(undefined). +%% floor/1 +%% Shadowed by erl_bif_types: erlang:floor/1 +-spec floor(Number) -> integer() when + Number :: number(). +floor(_) -> + erlang:nif_error(undef). + %% fun_info/2 -spec erlang:fun_info(Fun, Item) -> {Item, Info} when Fun :: function(), @@ -871,7 +901,7 @@ function_exported(_Module, _Function, _Arity) -> %% garbage_collect/0 -spec garbage_collect() -> true. garbage_collect() -> - erlang:nif_error(undefined). + erts_internal:garbage_collect(major). %% garbage_collect/1 -spec garbage_collect(Pid) -> GCResult when @@ -884,36 +914,39 @@ garbage_collect(Pid) -> error:Error -> erlang:error(Error, [Pid]) end. +-record(gcopt, { + async = sync :: sync | {async, _}, + type = major % default major, can also be minor + }). + %% garbage_collect/2 -spec garbage_collect(Pid, OptionList) -> GCResult | async when Pid :: pid(), RequestId :: term(), - Option :: {async, RequestId}, + Option :: {async, RequestId} | {type, 'major' | 'minor'}, OptionList :: [Option], GCResult :: boolean(). garbage_collect(Pid, OptionList) -> try - Async = get_gc_opts(OptionList, sync), - case Async of + GcOpts = get_gc_opts(OptionList, #gcopt{}), + case GcOpts#gcopt.async of {async, ReqId} -> {priority, Prio} = erlang:process_info(erlang:self(), priority), - erts_internal:request_system_task(Pid, - Prio, - {garbage_collect, ReqId}), + erts_internal:request_system_task( + Pid, Prio, {garbage_collect, ReqId, GcOpts#gcopt.type}), async; sync -> case Pid == erlang:self() of true -> - erlang:garbage_collect(); + erts_internal:garbage_collect(GcOpts#gcopt.type); false -> {priority, Prio} = erlang:process_info(erlang:self(), priority), ReqId = erlang:make_ref(), - erts_internal:request_system_task(Pid, - Prio, - {garbage_collect, - ReqId}), + erts_internal:request_system_task( + Pid, Prio, + {garbage_collect, ReqId, GcOpts#gcopt.type}), receive {garbage_collect, ReqId, GCResult} -> GCResult @@ -925,10 +958,12 @@ garbage_collect(Pid, OptionList) -> end. % gets async opt and verify valid option list -get_gc_opts([{async, _ReqId} = AsyncTuple | Options], _OldAsync) -> - get_gc_opts(Options, AsyncTuple); -get_gc_opts([], Async) -> - Async. +get_gc_opts([{async, _ReqId} = AsyncTuple | Options], GcOpt = #gcopt{}) -> + get_gc_opts(Options, GcOpt#gcopt{ async = AsyncTuple }); +get_gc_opts([{type, T} | Options], GcOpt = #gcopt{}) -> + get_gc_opts(Options, GcOpt#gcopt{ type = T }); +get_gc_opts([], GcOpt) -> + GcOpt. %% garbage_collect_message_area/0 -spec erlang:garbage_collect_message_area() -> boolean(). @@ -963,9 +998,10 @@ get_keys(_Val) -> erlang:nif_error(undefined). %% get_module_info/1 --spec erlang:get_module_info(P1) -> [{atom(), [{atom(), term()}]}] when - P1 :: atom(). -get_module_info(_P1) -> +-spec erlang:get_module_info(Module) -> [{Item, term()}] when + Item :: module | exports | attributes | compile | native | md5, + Module :: atom(). +get_module_info(_Module) -> erlang:nif_error(undefined). %% get_stacktrace/0 @@ -982,8 +1018,20 @@ group_leader() -> -spec group_leader(GroupLeader, Pid) -> true when GroupLeader :: pid(), Pid :: pid(). -group_leader(_GroupLeader, _Pid) -> - erlang:nif_error(undefined). +group_leader(GroupLeader, Pid) -> + case case erts_internal:group_leader(GroupLeader, Pid) of + false -> + Ref = erlang:make_ref(), + erts_internal:group_leader(GroupLeader, + Pid, + Ref), + receive {Ref, MsgRes} -> MsgRes end; + Res -> + Res + end of + true -> true; + Error -> erlang:error(Error, [GroupLeader, Pid]) + end. %% halt/0 %% Shadowed by erl_bif_types: erlang:halt/0 @@ -1007,16 +1055,9 @@ halt(Status) -> halt(_Status, _Options) -> erlang:nif_error(undefined). -%% hash/2 --spec erlang:hash(Term, Range) -> pos_integer() when - Term :: term(), - Range :: pos_integer(). -hash(_Term, _Range) -> - erlang:nif_error(undefined). - %% has_prepared_code_on_load/1 -spec erlang:has_prepared_code_on_load(PreparedCode) -> boolean() when - PreparedCode :: binary(). + PreparedCode :: prepared_code(). has_prepared_code_on_load(_PreparedCode) -> erlang:nif_error(undefined). @@ -1061,6 +1102,12 @@ iolist_size(_Item) -> iolist_to_binary(_IoListOrBinary) -> erlang:nif_error(undefined). +%% iolist_to_iovec/1 +-spec erlang:iolist_to_iovec(IoListOrBinary) -> iovec() when + IoListOrBinary :: iolist() | binary(). +iolist_to_iovec(_IoListOrBinary) -> + erlang:nif_error(undefined). + %% is_alive/0 -spec is_alive() -> boolean(). is_alive() -> @@ -1074,6 +1121,13 @@ is_alive() -> is_builtin(_Module, _Function, _Arity) -> erlang:nif_error(undefined). +%% Shadowed by erl_bif_types: erlang:is_map_key/2 +-spec is_map_key(Key, Map) -> boolean() when + Key :: term(), + Map :: map(). +is_map_key(_,_) -> + erlang:nif_error(undef). + %% is_process_alive/1 -spec is_process_alive(Pid) -> boolean() when Pid :: pid(). @@ -1142,6 +1196,18 @@ list_to_integer(_String,_Base) -> list_to_pid(_String) -> erlang:nif_error(undefined). +%% list_to_port/1 +-spec list_to_port(String) -> port() when + String :: string(). +list_to_port(_String) -> + erlang:nif_error(undefined). + +%% list_to_ref/1 +-spec list_to_ref(String) -> reference() when + String :: string(). +list_to_ref(_String) -> + erlang:nif_error(undefined). + %% list_to_tuple/1 -spec list_to_tuple(List) -> tuple() when List :: [term()]. @@ -1171,6 +1237,14 @@ make_ref() -> map_size(_Map) -> erlang:nif_error(undefined). +%% Shadowed by erl_bif_types: erlang:map_get/2 +-spec map_get(Key, Map) -> Value when + Map :: map(), + Key :: any(), + Value :: any(). +map_get(_Key, _Map) -> + erlang:nif_error(undefined). + %% match_spec_test/3 -spec erlang:match_spec_test(MatchAgainst, MatchSpec, Type) -> TestResult when MatchAgainst :: [term()] | tuple(), @@ -1313,7 +1387,7 @@ pid_to_list(_Pid) -> erlang:nif_error(undefined). %% port_to_list/1 --spec erlang:port_to_list(Port) -> string() when +-spec port_to_list(Port) -> string() when Port :: port(). port_to_list(_Port) -> erlang:nif_error(undefined). @@ -1433,7 +1507,7 @@ timestamp() -> -spec erlang:prepare_loading(Module, Code) -> PreparedCode | {error, Reason} when Module :: module(), Code :: binary(), - PreparedCode :: binary(), + PreparedCode :: prepared_code(), Reason :: bad_file. prepare_loading(_Module, _Code) -> erlang:nif_error(undefined). @@ -1447,8 +1521,21 @@ pre_loaded() -> -spec erlang:process_display(Pid, Type) -> true when Pid :: pid(), Type :: backtrace. -process_display(_Pid, _Type) -> - erlang:nif_error(undefined). +process_display(Pid, Type) -> + case case erts_internal:process_display(Pid, Type) of + Ref when erlang:is_reference(Ref) -> + receive + {Ref, Res} -> + Res + end; + Res -> + Res + end of + badarg -> + erlang:error(badarg, [Pid, Type]); + Result -> + Result + end. %% process_flag/3 -spec process_flag(Pid, Flag, Value) -> OldValue when @@ -1456,8 +1543,15 @@ process_display(_Pid, _Type) -> Flag :: save_calls, Value :: non_neg_integer(), OldValue :: non_neg_integer(). -process_flag(_Pid, _Flag, _Value) -> - erlang:nif_error(undefined). +process_flag(Pid, Flag, Value) -> + case case erts_internal:process_flag(Pid, Flag, Value) of + Ref when erlang:is_reference(Ref) -> + receive {Ref, Res} -> Res end; + Res -> Res + end of + badarg -> erlang:error(badarg, [Pid, Flag, Value]); + Result -> Result + end. %% process_info/1 -spec process_info(Pid) -> Info when @@ -1523,7 +1617,7 @@ read_timer(_TimerRef, _Options) -> erlang:nif_error(undefined). %% ref_to_list/1 --spec erlang:ref_to_list(Ref) -> string() when +-spec ref_to_list(Ref) -> string() when Ref :: reference(). ref_to_list(_Ref) -> erlang:nif_error(undefined). @@ -1611,12 +1705,26 @@ setnode(_P1, _P2) -> erlang:nif_error(undefined). %% setnode/3 --spec erlang:setnode(P1, P2, P3) -> true when - P1 :: atom(), - P2 :: port(), - P3 :: {term(), term(), term(), term()}. -setnode(_P1, _P2, _P3) -> - erlang:nif_error(undefined). +-spec erlang:setnode(Node, DistCtrlr, Opts) -> dist_handle() when + Node :: atom(), + DistCtrlr :: port() | pid(), + Opts :: {integer(), integer(), atom(), atom()}. +setnode(Node, DistCtrlr, {Flags, Ver, IC, OC} = Opts) when erlang:is_atom(IC), + erlang:is_atom(OC) -> + case case erts_internal:create_dist_channel(Node, DistCtrlr, + Flags, Ver) of + {ok, DH} -> DH; + {message, Ref} -> receive {Ref, Res} -> Res end; + Err -> Err + end of + Error when erlang:is_atom(Error) -> + erlang:error(Error, [Node, DistCtrlr, Opts]); + DHandle -> + DHandle + end; +setnode(Node, DistCtrlr, Opts) -> + erlang:error(badarg, [Node, DistCtrlr, Opts]). + %% size/1 %% Shadowed by erl_bif_types: erlang:size/1 @@ -1675,9 +1783,32 @@ start_timer(_Time, _Dest, _Msg, _Options) -> -spec erlang:suspend_process(Suspendee, OptList) -> boolean() when Suspendee :: pid(), OptList :: [Opt], - Opt :: unless_suspending | asynchronous. -suspend_process(_Suspendee, _OptList) -> - erlang:nif_error(undefined). + Opt :: unless_suspending | asynchronous | {asynchronous, term()}. +suspend_process(Suspendee, OptList) -> + case case erts_internal:suspend_process(Suspendee, OptList) of + Ref when erlang:is_reference(Ref) -> + receive {Ref, Res} -> Res end; + Res -> + Res + end of + true -> true; + false -> false; + Error -> erlang:error(Error, [Suspendee, OptList]) + end. + +-spec erlang:suspend_process(Suspendee) -> 'true' when + Suspendee :: pid(). +suspend_process(Suspendee) -> + case case erts_internal:suspend_process(Suspendee, []) of + Ref when erlang:is_reference(Ref) -> + receive {Ref, Res} -> Res end; + Res -> + Res + end of + true -> true; + false -> erlang:error(internal_error, [Suspendee]); + Error -> erlang:error(Error, [Suspendee]) + end. %% system_monitor/0 -spec erlang:system_monitor() -> MonSettings when @@ -1862,10 +1993,12 @@ element(_N, _Tuple) -> erlang:nif_error(undefined). %% Not documented +-type module_info_key() :: attributes | compile | exports | functions | md5 + | module | native | native_addresses | nifs. -spec erlang:get_module_info(Module, Item) -> ModuleInfo when Module :: atom(), - Item :: module | exports | functions | attributes | compile | native_addresses | md5, - ModuleInfo :: atom() | [] | [{atom(), arity()}] | [{atom(), term()}] | [{atom(), arity(), integer()}]. + Item :: module_info_key(), + ModuleInfo :: term(). get_module_info(_Module, _Item) -> erlang:nif_error(undefined). @@ -1991,8 +2124,8 @@ load_module(Mod, Code) -> case erlang:prepare_loading(Mod, Code) of {error,_}=Error -> Error; - Bin when erlang:is_binary(Bin) -> - case erlang:finish_loading([Bin]) of + Prep when erlang:is_reference(Prep) -> + case erlang:finish_loading([Prep]) of ok -> {module,Mod}; {Error,[Mod]} -> @@ -2058,7 +2191,7 @@ nodes(_Arg) -> | stream | {line, L :: non_neg_integer()} | {cd, Dir :: string() | binary()} - | {env, Env :: [{Name :: string(), Val :: string() | false}]} + | {env, Env :: [{Name :: os:env_var_name(), Val :: os:env_var_value() | false}]} | {args, [string() | binary()]} | {arg0, string() | binary()} | exit_status @@ -2183,7 +2316,7 @@ process_flag(_Flag, _Value) -> {min_heap_size, MinHeapSize :: non_neg_integer()} | {min_bin_vheap_size, MinBinVHeapSize :: non_neg_integer()} | {max_heap_size, MaxHeapSize :: max_heap_size()} | - {monitored_by, Pids :: [pid()]} | + {monitored_by, MonitoredBy :: [pid() | port() | nif_resource()]} | {monitors, Monitors :: [{process | port, Pid :: pid() | port() | {RegName :: atom(), Node :: node()}}]} | @@ -2274,6 +2407,8 @@ spawn_opt(_Tuple) -> -spec statistics(active_tasks) -> [ActiveTasks] when ActiveTasks :: non_neg_integer(); + (active_tasks_all) -> [ActiveTasks] when + ActiveTasks :: non_neg_integer(); (context_switches) -> {ContextSwitches,0} when ContextSwitches :: non_neg_integer(); (exact_reductions) -> {Total_Exact_Reductions, @@ -2290,7 +2425,8 @@ spawn_opt(_Tuple) -> MSAcc_Thread :: #{ type := MSAcc_Thread_Type, id := MSAcc_Thread_Id, counters := MSAcc_Counters}, - MSAcc_Thread_Type :: scheduler | async | aux, + MSAcc_Thread_Type :: async | aux | dirty_io_scheduler + | dirty_cpu_scheduler | poll | scheduler, MSAcc_Thread_Id :: non_neg_integer(), MSAcc_Counters :: #{ MSAcc_Thread_State => non_neg_integer() }, MSAcc_Thread_State :: alloc | aux | bif | busy_wait | check_io | @@ -2301,8 +2437,10 @@ spawn_opt(_Tuple) -> Total_Reductions :: non_neg_integer(), Reductions_Since_Last_Call :: non_neg_integer(); (run_queue) -> non_neg_integer(); - (run_queue_lengths) -> [RunQueueLenght] when - RunQueueLenght :: non_neg_integer(); + (run_queue_lengths) -> [RunQueueLength] when + RunQueueLength :: non_neg_integer(); + (run_queue_lengths_all) -> [RunQueueLength] when + RunQueueLength :: non_neg_integer(); (runtime) -> {Total_Run_Time, Time_Since_Last_Call} when Total_Run_Time :: non_neg_integer(), Time_Since_Last_Call :: non_neg_integer(); @@ -2310,10 +2448,18 @@ spawn_opt(_Tuple) -> SchedulerId :: pos_integer(), ActiveTime :: non_neg_integer(), TotalTime :: non_neg_integer(); + (scheduler_wall_time_all) -> [{SchedulerId, ActiveTime, TotalTime}] | undefined when + SchedulerId :: pos_integer(), + ActiveTime :: non_neg_integer(), + TotalTime :: non_neg_integer(); (total_active_tasks) -> ActiveTasks when + ActiveTasks :: non_neg_integer(); + (total_active_tasks_all) -> ActiveTasks when ActiveTasks :: non_neg_integer(); - (total_run_queue_lengths) -> TotalRunQueueLenghts when - TotalRunQueueLenghts :: non_neg_integer(); + (total_run_queue_lengths) -> TotalRunQueueLengths when + TotalRunQueueLengths :: non_neg_integer(); + (total_run_queue_lengths_all) -> TotalRunQueueLengths when + TotalRunQueueLengths :: non_neg_integer(); (wall_clock) -> {Total_Wallclock_Time, Wallclock_Time_Since_Last_Call} when Total_Wallclock_Time :: non_neg_integer(), @@ -2347,6 +2493,10 @@ subtract(_,_) -> OldDirtyCPUSchedulersOnline when DirtyCPUSchedulersOnline :: pos_integer(), OldDirtyCPUSchedulersOnline :: pos_integer(); + (erts_alloc, {Alloc, F, V}) -> ok | notsup when + Alloc :: atom(), + F :: atom(), + V :: integer(); (fullsweep_after, Number) -> OldNumber when Number :: non_neg_integer(), OldNumber :: non_neg_integer(); @@ -2398,7 +2548,7 @@ term_to_binary(_Term) -> Term :: term(), Options :: [compressed | {compressed, Level :: 0..9} | - {minor_version, Version :: 0..1} ]. + {minor_version, Version :: 0..2} ]. term_to_binary(_Term, _Options) -> erlang:nif_error(undefined). @@ -2408,10 +2558,11 @@ term_to_binary(_Term, _Options) -> tl(_List) -> erlang:nif_error(undefined). +-type match_variable() :: atom(). % Approximation of '$1' | '$2' | ... -type trace_pattern_mfa() :: {atom(),atom(),arity() | '_'} | on_load. -type trace_match_spec() :: - [{[term()] | '_' ,[term()],[term()]}]. + [{[term()] | '_' | match_variable() ,[term()],[term()]}]. -spec erlang:trace_pattern(MFA, MatchSpec) -> non_neg_integer() when MFA :: trace_pattern_mfa() | send | 'receive', @@ -2502,12 +2653,14 @@ tuple_to_list(_Tuple) -> Settings :: [{Subsystem :: atom(), [{Parameter :: atom(), Value :: term()}]}]; - (alloc_util_allocators) -> [Alloc] when - Alloc :: atom(); ({allocator, Alloc}) -> [_] when %% More or less anything Alloc :: atom(); + (alloc_util_allocators) -> [Alloc] when + Alloc :: atom(); ({allocator_sizes, Alloc}) -> [_] when %% More or less anything Alloc :: atom(); + (atom_count) -> pos_integer(); + (atom_limit) -> pos_integer(); (build_type) -> opt | debug | purify | quantify | purecov | gcov | valgrind | gprof | lcnt | frmptr; (c_compiler_used) -> {atom(), term()}; @@ -2528,10 +2681,12 @@ tuple_to_list(_Tuple) -> (dist_ctrl) -> {Node :: node(), ControllingEntity :: port() | pid()}; (driver_version) -> string(); - (dynamic_trace) -> none | dtrace | systemtap; + (dynamic_trace) -> none | dtrace | systemtap; (dynamic_trace_probes) -> boolean(); + (end_time) -> non_neg_integer(); (elib_malloc) -> false; (eager_check_io) -> boolean(); + (ets_count) -> pos_integer(); (ets_limit) -> pos_integer(); (fullsweep_after) -> {fullsweep_after, non_neg_integer()}; (garbage_collection) -> [{atom(), integer()}]; @@ -2557,6 +2712,7 @@ tuple_to_list(_Tuple) -> (otp_release) -> string(); (os_monotonic_time_source) -> [{atom(),term()}]; (os_system_time_source) -> [{atom(),term()}]; + (port_parallelism) -> boolean(); (port_count) -> non_neg_integer(); (port_limit) -> pos_integer(); (process_count) -> pos_integer(); @@ -2586,7 +2742,8 @@ tuple_to_list(_Tuple) -> (trace_control_word) -> non_neg_integer(); (update_cpu_info) -> changed | unchanged; (version) -> string(); - (wordsize | {wordsize, internal} | {wordsize, external}) -> 4 | 8. + (wordsize | {wordsize, internal} | {wordsize, external}) -> 4 | 8; + (overview) -> boolean(). system_info(_Item) -> erlang:nif_error(undefined). @@ -2946,15 +3103,6 @@ send_nosuspend(Pid, Msg, Opts) -> localtime_to_universaltime(Localtime) -> erlang:localtime_to_universaltime(Localtime, undefined). --spec erlang:suspend_process(Suspendee) -> 'true' when - Suspendee :: pid(). -suspend_process(P) -> - case catch erlang:suspend_process(P, []) of - {'EXIT', {Reason, _}} -> erlang:error(Reason, [P]); - {'EXIT', Reason} -> erlang:error(Reason, [P]); - Res -> Res - end. - %% %% Port BIFs %% @@ -3157,33 +3305,51 @@ port_get_data(_Port) -> erlang:nif_error(undefined). %% -%% If the emulator wants to perform a distributed command and -%% a connection is not established to the actual node the following -%% functions are called in order to set up the connection and then -%% reactivate the command. +%% Distribution channel management %% --spec erlang:dlink(pid() | port()) -> 'true'. -dlink(Pid) -> - case net_kernel:connect(erlang:node(Pid)) of - true -> erlang:link(Pid); - false -> erlang:dist_exit(erlang:self(), noconnection, Pid), true - end. +-spec erlang:dist_ctrl_input_handler(DHandle, InputHandler) -> 'ok' when + DHandle :: dist_handle(), + InputHandler :: pid(). -%% Can this ever happen? --spec erlang:dunlink(identifier()) -> 'true'. -dunlink(Pid) -> - case net_kernel:connect(erlang:node(Pid)) of - true -> erlang:unlink(Pid); - false -> true - end. +dist_ctrl_input_handler(_DHandle, _InputHandler) -> + erlang:nif_error(undefined). -dmonitor_node(Node, Flag, []) -> - case net_kernel:connect(Node) of - true -> erlang:monitor_node(Node, Flag, []); - false -> erlang:self() ! {nodedown, Node}, true - end; +-spec erlang:dist_ctrl_put_data(DHandle, Data) -> 'ok' when + DHandle :: dist_handle(), + Data :: iodata(). + +dist_ctrl_put_data(_DHandle, _Data) -> + erlang:nif_error(undefined). + +-spec erlang:dist_ctrl_get_data(DHandle) -> Data | 'none' when + DHandle :: dist_handle(), + Data :: iodata(). + +dist_ctrl_get_data(_DHandle) -> + erlang:nif_error(undefined). + +-spec erlang:dist_ctrl_get_data_notification(DHandle) -> 'ok' when + DHandle :: dist_handle(). + +dist_ctrl_get_data_notification(_DHandle) -> + erlang:nif_error(undefined). + +-spec erlang:dist_get_stat(DHandle) -> Res when + DHandle :: dist_handle(), + InputPackets :: non_neg_integer(), + OutputPackets :: non_neg_integer(), + PendingOutputPackets :: boolean(), + Res :: {'ok', InputPackets, OutputPackets, PendingOutputPackets}. + +dist_get_stat(_DHandle) -> + erlang:nif_error(undefined). + +dmonitor_node(Node, _Flag, []) -> + %% Only called when auto-connect attempt failed early in VM + erlang:self() ! {nodedown, Node}, + true; dmonitor_node(Node, Flag, Opts) -> case lists:member(allow_passive_connect, Opts) of true -> @@ -3195,72 +3361,6 @@ dmonitor_node(Node, Flag, Opts) -> dmonitor_node(Node,Flag,[]) end. -dgroup_leader(Leader, Pid) -> - case net_kernel:connect(erlang:node(Pid)) of - true -> erlang:group_leader(Leader, Pid); - false -> true %% bad arg ? - end. - -dexit(Pid, Reason) -> - case net_kernel:connect(erlang:node(Pid)) of - true -> erlang:exit(Pid, Reason); - false -> true - end. - -dsend(Pid, Msg) when erlang:is_pid(Pid) -> - case net_kernel:connect(erlang:node(Pid)) of - true -> erlang:send(Pid, Msg); - false -> Msg - end; -dsend(Port, Msg) when erlang:is_port(Port) -> - case net_kernel:connect(erlang:node(Port)) of - true -> erlang:send(Port, Msg); - false -> Msg - end; -dsend({Name, Node}, Msg) -> - case net_kernel:connect(Node) of - true -> erlang:send({Name,Node}, Msg); - false -> Msg; - ignored -> Msg % Not distributed. - end. - -dsend(Pid, Msg, Opts) when erlang:is_pid(Pid) -> - case net_kernel:connect(erlang:node(Pid)) of - true -> erlang:send(Pid, Msg, Opts); - false -> ok - end; -dsend(Port, Msg, Opts) when erlang:is_port(Port) -> - case net_kernel:connect(erlang:node(Port)) of - true -> erlang:send(Port, Msg, Opts); - false -> ok - end; -dsend({Name, Node}, Msg, Opts) -> - case net_kernel:connect(Node) of - true -> erlang:send({Name,Node}, Msg, Opts); - false -> ok; - ignored -> ok % Not distributed. - end. - --spec erlang:dmonitor_p('process', pid() | {atom(),atom()}) -> reference(). -dmonitor_p(process, ProcSpec) -> - %% ProcSpec = pid() | {atom(),atom()} - %% ProcSpec CANNOT be an atom because a locally registered process - %% is never handled here. - Node = case ProcSpec of - {S,N} when erlang:is_atom(S), - erlang:is_atom(N), - N =/= erlang:node() -> N; - _ when erlang:is_pid(ProcSpec) -> erlang:node(ProcSpec) - end, - case net_kernel:connect(Node) of - true -> - erlang:monitor(process, ProcSpec); - false -> - Ref = erlang:make_ref(), - erlang:self() ! {'DOWN', Ref, process, ProcSpec, noconnection}, - Ref - end. - %% %% Trap function used when modified timing has been enabled. %% @@ -3493,33 +3593,6 @@ rvrs(Xs) -> rvrs(Xs, []). rvrs([],Ys) -> Ys; rvrs([X|Xs],Ys) -> rvrs(Xs, [X|Ys]). -%% erlang:await_proc_exit/3 is for internal use only! -%% -%% BIFs that need to await a specific process exit before -%% returning traps to erlang:await_proc_exit/3. -%% -%% NOTE: This function is tightly coupled to -%% the implementation of the -%% erts_bif_prep_await_proc_exit_*() -%% functions in bif.c. Do not make -%% any changes to it without reading -%% the comment about them in bif.c! --spec erlang:await_proc_exit(dst(), 'apply' | 'data' | 'reason', term()) -> term(). -await_proc_exit(Proc, Op, Data) -> - Mon = erlang:monitor(process, Proc), - receive - {'DOWN', Mon, process, _Proc, Reason} -> - case Op of - apply -> - {M, F, A} = Data, - erlang:apply(M, F, A); - data -> - Data; - reason -> - Reason - end - end. - -spec min(Term1, Term2) -> Minimum when Term1 :: term(), Term2 :: term(), @@ -3543,11 +3616,9 @@ max(A, _) -> A. %% -type memory_type() :: 'total' | 'processes' | 'processes_used' | 'system' - | 'atom' | 'atom_used' | 'binary' | 'code' | 'ets' - | 'low' | 'maximum'. + | 'atom' | 'atom_used' | 'binary' | 'code' | 'ets'. -define(CARRIER_ALLOCS, [mseg_alloc]). --define(LOW_ALLOCS, [ll_low_alloc, std_low_alloc]). -define(ALL_NEEDED_ALLOCS, (erlang:system_info(alloc_util_allocators) -- ?CARRIER_ALLOCS)). @@ -3559,9 +3630,7 @@ max(A, _) -> A. atom_used = 0, binary = 0, code = 0, - ets = 0, - low = 0, - maximum = 0}). + ets = 0}). -spec erlang:memory() -> [{Type, Size}] when Type :: memory_type(), @@ -3571,14 +3640,6 @@ memory() -> notsup -> erlang:error(notsup); Mem -> - InstrTail = case Mem#memory.maximum of - 0 -> []; - _ -> [{maximum, Mem#memory.maximum}] - end, - Tail = case Mem#memory.low of - 0 -> InstrTail; - _ -> [{low, Mem#memory.low} | InstrTail] - end, [{total, Mem#memory.total}, {processes, Mem#memory.processes}, {processes_used, Mem#memory.processes_used}, @@ -3587,7 +3648,7 @@ memory() -> {atom_used, Mem#memory.atom_used}, {binary, Mem#memory.binary}, {code, Mem#memory.code}, - {ets, Mem#memory.ets} | Tail] + {ets, Mem#memory.ets}] end. -spec erlang:memory(Type :: memory_type()) -> non_neg_integer(); @@ -3675,16 +3736,6 @@ need_mem_info(binary) -> {false, [binary_alloc], true, false}; need_mem_info(ets) -> {true, [ets_alloc], true, false}; -need_mem_info(low) -> - LowAllocs = ?LOW_ALLOCS -- ?CARRIER_ALLOCS, - {_, _, FeatureList, _} = erlang:system_info(allocator), - AlcUAllocs = case LowAllocs -- FeatureList of - [] -> LowAllocs; - _ -> [] - end, - {false, AlcUAllocs, true, true}; -need_mem_info(maximum) -> - {true, [], true, true}; need_mem_info(_) -> {false, [], false, true}. @@ -3697,8 +3748,6 @@ get_memval(atom_used, #memory{atom_used = V}) -> V; get_memval(binary, #memory{binary = V}) -> V; get_memval(code, #memory{code = V}) -> V; get_memval(ets, #memory{ets = V}) -> V; -get_memval(low, #memory{low = V}) -> V; -get_memval(maximum, #memory{maximum = V}) -> V; get_memval(_, #memory{}) -> 0. memory_is_supported() -> @@ -3712,15 +3761,14 @@ memory_is_supported() -> get_blocks_size([{blocks_size, Sz, _, _} | Rest], Acc) -> get_blocks_size(Rest, Acc+Sz); -get_blocks_size([{_, _, _, _} | Rest], Acc) -> - get_blocks_size(Rest, Acc); get_blocks_size([{blocks_size, Sz} | Rest], Acc) -> get_blocks_size(Rest, Acc+Sz); -get_blocks_size([{_, _} | Rest], Acc) -> +get_blocks_size([_ | Rest], Acc) -> get_blocks_size(Rest, Acc); get_blocks_size([], Acc) -> Acc. + blocks_size([{Carriers, SizeList} | Rest], Acc) when Carriers == mbcs; Carriers == mbcs_pool; Carriers == sbcs -> @@ -3731,8 +3779,8 @@ blocks_size([], Acc) -> Acc. get_fix_proc([{ProcType, A1, U1}| Rest], {A0, U0}) when ProcType == proc; - ProcType == monitor_sh; - ProcType == nlink_sh; + ProcType == monitor; + ProcType == link; ProcType == msg_ref; ProcType == ll_ptimer; ProcType == hl_ptimer; @@ -3754,16 +3802,6 @@ fix_proc([_ | Rest], Acc) -> fix_proc([], Acc) -> Acc. -is_low_alloc(_A, []) -> - false; -is_low_alloc(A, [A|_As]) -> - true; -is_low_alloc(A, [_A|As]) -> - is_low_alloc(A, As). - -is_low_alloc(A) -> - is_low_alloc(A, ?LOW_ALLOCS). - au_mem_data(notsup, _) -> notsup; au_mem_data(_, [{_, false} | _]) -> @@ -3816,16 +3854,11 @@ au_mem_data(#memory{total = Tot, Rest) end; au_mem_data(#memory{total = Tot, - system = Sys, - low = Low} = Mem, - [{A, _, Data} | Rest]) -> + system = Sys} = Mem, + [{_, _, Data} | Rest]) -> Sz = blocks_size(Data, 0), au_mem_data(Mem#memory{total = Tot+Sz, - system = Sys+Sz, - low = case is_low_alloc(A) of - true -> Low+Sz; - false -> Low - end}, + system = Sys+Sz}, Rest); au_mem_data(EMD, []) -> EMD. @@ -3847,10 +3880,6 @@ receive_emd(Ref) -> receive_emd(Ref, #memory{}, erlang:system_info(schedulers)). aa_mem_data(#memory{} = Mem, - [{maximum, Max} | Rest]) -> - aa_mem_data(Mem#memory{maximum = Max}, - Rest); -aa_mem_data(#memory{} = Mem, [{total, Tot} | Rest]) -> aa_mem_data(Mem#memory{total = Tot, system = 0}, % system will be adjusted later @@ -3875,7 +3904,6 @@ aa_mem_data(#memory{processes = Proc, processes_used = ProcU, system = Sys} = Mem, [{ProcData, Sz} | Rest]) when ProcData == bif_timer; - ProcData == link_lh; ProcData == process_table -> aa_mem_data(Mem#memory{processes = Proc+Sz, processes_used = ProcU+Sz, @@ -3960,37 +3988,6 @@ receive_allocator(Ref, N, Acc) -> receive_allocator(Ref, N-1, insert_info(InfoList, Acc)) end. --spec erlang:await_sched_wall_time_modifications(Ref, Result) -> boolean() when - Ref :: reference(), - Result :: boolean(). - -await_sched_wall_time_modifications(Ref, Result) -> - sched_wall_time(Ref, erlang:system_info(schedulers)), - Result. - --spec erlang:gather_sched_wall_time_result(Ref) -> [{pos_integer(), - non_neg_integer(), - non_neg_integer()}] when - Ref :: reference(). - -gather_sched_wall_time_result(Ref) when erlang:is_reference(Ref) -> - sched_wall_time(Ref, erlang:system_info(schedulers), []). - -sched_wall_time(_Ref, 0) -> - ok; -sched_wall_time(Ref, N) -> - receive Ref -> sched_wall_time(Ref, N-1) end. - -sched_wall_time(_Ref, 0, Acc) -> - Acc; -sched_wall_time(Ref, N, undefined) -> - receive {Ref, _} -> sched_wall_time(Ref, N-1, undefined) end; -sched_wall_time(Ref, N, Acc) -> - receive - {Ref, undefined} -> sched_wall_time(Ref, N-1, undefined); - {Ref, SWT} -> sched_wall_time(Ref, N-1, [SWT|Acc]) - end. - -spec erlang:gather_gc_info_result(Ref) -> {number(),number(),0} when Ref :: reference(). @@ -4004,4 +4001,3 @@ gc_info(Ref, N, {OrigColls,OrigRecl}) -> {Ref, {_,Colls, Recl}} -> gc_info(Ref, N-1, {Colls+OrigColls,Recl+OrigRecl}) end. - diff --git a/erts/preloaded/src/erts.app.src b/erts/preloaded/src/erts.app.src index 7ab06164b4..ab0b9494b0 100644 --- a/erts/preloaded/src/erts.app.src +++ b/erts/preloaded/src/erts.app.src @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -28,16 +28,19 @@ init, otp_ring0, erts_code_purger, + prim_buffer, prim_eval, prim_file, prim_inet, prim_zip, + atomics, + counters, zlib ]}, {registered, []}, {applications, []}, {env, []}, - {runtime_dependencies, ["stdlib-3.0", "kernel-5.0", "sasl-3.0.1"]} + {runtime_dependencies, ["stdlib-3.5", "kernel-6.1", "sasl-3.3"]} ]}. %% vim: ft=erlang diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl index 28d71fd07e..c41532ed87 100644 --- a/erts/preloaded/src/erts_code_purger.erl +++ b/erts/preloaded/src/erts_code_purger.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2016. All Rights Reserved. +%% Copyright Ericsson AB 2016-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -25,33 +25,38 @@ -export([start/0, purge/1, soft_purge/1, pending_purge_lambda/3, finish_after_on_load/2]). --spec start() -> term(). +-spec start() -> no_return(). start() -> register(erts_code_purger, self()), process_flag(trap_exit, true), - loop(). - -loop() -> - _ = receive - {purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> - Res = do_purge(Mod), - From ! {reply, purge, Res, Ref}; - - {soft_purge,Mod,From,Ref} when is_atom(Mod), is_pid(From) -> - Res = do_soft_purge(Mod), - From ! {reply, soft_purge, Res, Ref}; - - {finish_after_on_load,{Mod,Keep},From,Ref} - when is_atom(Mod), is_pid(From) -> - Res = do_finish_after_on_load(Mod, Keep), - From ! {reply, finish_after_on_load, Res, Ref}; - - {test_purge, Mod, From, Type, Ref} when is_atom(Mod), is_pid(From) -> - do_test_purge(Mod, From, Type, Ref); - - _Other -> ignore - end, - loop(). + wait_for_request(). + +wait_for_request() -> + handle_request(receive Msg -> Msg end, []). + +handle_request({purge, Mod, From, Ref}, Reqs) when is_atom(Mod), is_pid(From) -> + {Res, NewReqs} = do_purge(Mod, Reqs), + From ! {reply, purge, Res, Ref}, + check_requests(NewReqs); +handle_request({soft_purge, Mod, From, Ref}, Reqs) when is_atom(Mod), is_pid(From) -> + {Res, NewReqs} = do_soft_purge(Mod, Reqs), + From ! {reply, soft_purge, Res, Ref}, + check_requests(NewReqs); +handle_request({finish_after_on_load, {Mod,Keep}, From, Ref}, Reqs) + when is_atom(Mod), is_boolean(Keep), is_pid(From) -> + NewReqs = do_finish_after_on_load(Mod, Keep, Reqs), + From ! {reply, finish_after_on_load, ok, Ref}, + check_requests(NewReqs); +handle_request({test_purge, Mod, From, Type, Ref}, Reqs) when is_atom(Mod), is_pid(From) -> + NewReqs = do_test_purge(Mod, From, Type, Ref, Reqs), + check_requests(NewReqs); +handle_request(_Garbage, Reqs) -> + check_requests(Reqs). + +check_requests([]) -> + wait_for_request(); +check_requests([R|Rs]) -> + handle_request(R, Rs). %% %% Processes that tries to call a fun that belongs to @@ -99,14 +104,15 @@ purge(Mod) when is_atom(Mod) -> Result end. -do_purge(Mod) -> +do_purge(Mod, Reqs) -> case erts_internal:purge_module(Mod, prepare) of false -> - {false, false}; + {{false, false}, Reqs}; true -> - DidKill = check_proc_code(erlang:processes(), Mod, true), + {DidKill, NewReqs} = check_proc_code(erlang:processes(), + Mod, true, Reqs), true = erts_internal:purge_module(Mod, complete), - {true, DidKill} + {{true, DidKill}, NewReqs} end. %% soft_purge(Module) @@ -122,17 +128,14 @@ soft_purge(Mod) -> Result end. -do_soft_purge(Mod) -> +do_soft_purge(Mod, Reqs) -> case erts_internal:purge_module(Mod, prepare) of false -> - true; + {true, Reqs}; true -> - Res = check_proc_code(erlang:processes(), Mod, false), - erts_internal:purge_module(Mod, - case Res of - false -> abort; - true -> complete - end) + {PurgeOp, NewReqs} = check_proc_code(erlang:processes(), + Mod, false, Reqs), + {erts_internal:purge_module(Mod, PurgeOp), NewReqs} end. %% finish_after_on_load(Module, Keep) @@ -147,179 +150,130 @@ finish_after_on_load(Mod, Keep) -> Result end. -do_finish_after_on_load(Mod, Keep) -> +do_finish_after_on_load(Mod, Keep, Reqs) -> erlang:finish_after_on_load(Mod, Keep), case Keep of true -> - ok; + Reqs; false -> case erts_internal:purge_module(Mod, prepare_on_load) of false -> - true; + Reqs; true -> - _ = check_proc_code(erlang:processes(), Mod, true), - true = erts_internal:purge_module(Mod, complete) + {_DidKill, NewReqs} = + check_proc_code(erlang:processes(), + Mod, true, Reqs), + true = erts_internal:purge_module(Mod, complete), + NewReqs end end. - %% -%% check_proc_code(Pids, Mod, Hard) - Send asynchronous +%% check_proc_code(Pids, Mod, Hard, Preqs) - Send asynchronous %% requests to all processes to perform a check_process_code %% operation. Each process will check their own state and %% reply with the result. If 'Hard' equals %% - true, processes that refer 'Mod' will be killed. If %% any processes were killed true is returned; otherwise, %% false. -%% - false, and any processes refer 'Mod', false will -%% returned; otherwise, true. +%% - false, and any processes refer 'Mod', 'abort' will +%% be returned; otherwise, 'complete'. %% -%% Requests will be sent to all processes identified by -%% Pids at once, but without allowing GC to be performed. -%% Check process code operations that are aborted due to -%% GC need, will be restarted allowing GC. However, only -%% ?MAX_CPC_GC_PROCS outstanding operation allowing GC at -%% a time will be allowed. This in order not to blow up -%% memory wise. -%% -%% We also only allow ?MAX_CPC_NO_OUTSTANDING_KILLS +%% We only allow ?MAX_CPC_NO_OUTSTANDING_KILLS %% outstanding kills. This both in order to avoid flooding %% our message queue with 'DOWN' messages and limiting the %% amount of memory used to keep references to all %% outstanding kills. %% -%% We maybe should allow more than two outstanding -%% GC requests, but for now we play it safe... --define(MAX_CPC_GC_PROCS, 2). -define(MAX_CPC_NO_OUTSTANDING_KILLS, 10). --record(cpc_static, {hard, module, tag}). +-record(cpc_static, {hard, module, tag, purge_requests}). -record(cpc_kill, {outstanding = [], no_outstanding = 0, waiting = [], killed = false}). -check_proc_code(Pids, Mod, Hard) -> +check_proc_code(Pids, Mod, Hard, PReqs) -> Tag = erlang:make_ref(), CpcS = #cpc_static{hard = Hard, module = Mod, - tag = Tag}, - check_proc_code(CpcS, cpc_init(CpcS, Pids, 0), 0, [], #cpc_kill{}, true). - -check_proc_code(#cpc_static{hard = true}, 0, 0, [], - #cpc_kill{outstanding = [], waiting = [], killed = Killed}, - true) -> - %% No outstanding requests. We did a hard check, so result is whether or - %% not we killed any processes... - Killed; -check_proc_code(#cpc_static{hard = false}, 0, 0, [], _KillState, Success) -> - %% No outstanding requests and we did a soft check... - Success; -check_proc_code(#cpc_static{hard = false, tag = Tag} = CpcS, NoReq0, NoGcReq0, - [], _KillState, false) -> - %% Failed soft check; just cleanup the remaining replies corresponding - %% to the requests we've sent... - {NoReq1, NoGcReq1} = receive - {check_process_code, {Tag, _P, GC}, _Res} -> - case GC of - false -> {NoReq0-1, NoGcReq0}; - true -> {NoReq0, NoGcReq0-1} - end - end, - check_proc_code(CpcS, NoReq1, NoGcReq1, [], _KillState, false); -check_proc_code(#cpc_static{tag = Tag} = CpcS, NoReq0, NoGcReq0, NeedGC0, - KillState0, Success) -> - - %% Check if we should request a GC operation - {NoGcReq1, NeedGC1} = case NoGcReq0 < ?MAX_CPC_GC_PROCS of - GcOpAllowed when GcOpAllowed == false; - NeedGC0 == [] -> - {NoGcReq0, NeedGC0}; - _ -> - {NoGcReq0+1, cpc_request_gc(CpcS,NeedGC0)} - end, - - %% Wait for a cpc reply or 'DOWN' message - {NoReq1, NoGcReq2, Pid, Result, KillState1} = cpc_recv(Tag, - NoReq0, - NoGcReq1, - KillState0), - - %% Check the result of the reply - case Result of - aborted -> - %% Operation aborted due to the need to GC in order to - %% determine if the process is referring the module. - %% Schedule the operation for restart allowing GC... - check_proc_code(CpcS, NoReq1, NoGcReq2, [Pid|NeedGC1], KillState1, - Success); - false -> + tag = Tag, + purge_requests = PReqs}, + cpc_receive(CpcS, cpc_init(CpcS, Pids, 0), #cpc_kill{}, []). + +cpc_receive(#cpc_static{hard = true} = CpcS, + 0, + #cpc_kill{outstanding = [], waiting = [], killed = Killed}, + PReqs) -> + %% No outstanding cpc requests. We did a hard check, so result is + %% whether or not we killed any processes... + cpc_result(CpcS, PReqs, Killed); +cpc_receive(#cpc_static{hard = false} = CpcS, 0, _KillState, PReqs) -> + %% No outstanding cpc requests and we did a soft check that succeeded... + cpc_result(CpcS, PReqs, complete); +cpc_receive(#cpc_static{tag = Tag} = CpcS, NoReq, KillState0, PReqs) -> + receive + {check_process_code, {Tag, _Pid}, false} -> %% Process not referring the module; done with this process... - check_proc_code(CpcS, NoReq1, NoGcReq2, NeedGC1, KillState1, - Success); - true -> + cpc_receive(CpcS, NoReq-1, KillState0, PReqs); + {check_process_code, {Tag, Pid}, true} -> %% Process referring the module... case CpcS#cpc_static.hard of false -> %% ... and soft check. The whole operation failed so - %% no point continuing; clean up and fail... - check_proc_code(CpcS, NoReq1, NoGcReq2, [], KillState1, - false); + %% no point continuing; fail straight away. Garbage + %% messages from this session will be ignored + %% by following sessions... + cpc_result(CpcS, PReqs, abort); true -> %% ... and hard check; schedule kill of it... - check_proc_code(CpcS, NoReq1, NoGcReq2, NeedGC1, - cpc_sched_kill(Pid, KillState1), Success) + KillState1 = cpc_sched_kill(Pid, KillState0), + cpc_receive(CpcS, NoReq-1, KillState1, PReqs) end; - 'DOWN' -> - %% Handled 'DOWN' message - check_proc_code(CpcS, NoReq1, NoGcReq2, NeedGC1, - KillState1, Success) + {'DOWN', MonRef, process, _, _} -> + KillState1 = cpc_handle_down(MonRef, KillState0), + cpc_receive(CpcS, NoReq, KillState1, PReqs); + PReq when element(1, PReq) == purge; + element(1, PReq) == soft_purge; + element(1, PReq) == test_purge -> + %% A new purge request; save it until later... + cpc_receive(CpcS, NoReq, KillState0, [PReq | PReqs]); + _Garbage -> + %% Garbage message; ignore it... + cpc_receive(CpcS, NoReq, KillState0, PReqs) end. -cpc_recv(Tag, NoReq, NoGcReq, #cpc_kill{outstanding = []} = KillState) -> - receive - {check_process_code, {Tag, Pid, GC}, Res} -> - cpc_handle_cpc(NoReq, NoGcReq, GC, Pid, Res, KillState) - end; -cpc_recv(Tag, NoReq, NoGcReq, - #cpc_kill{outstanding = [R0, R1, R2, R3, R4 | _]} = KillState) -> - receive - {'DOWN', R, process, _, _} when R == R0; - R == R1; - R == R2; - R == R3; - R == R4 -> - cpc_handle_down(NoReq, NoGcReq, R, KillState); - {check_process_code, {Tag, Pid, GC}, Res} -> - cpc_handle_cpc(NoReq, NoGcReq, GC, Pid, Res, KillState) - end; -cpc_recv(Tag, NoReq, NoGcReq, #cpc_kill{outstanding = [R|_]} = KillState) -> - receive - {'DOWN', R, process, _, _} -> - cpc_handle_down(NoReq, NoGcReq, R, KillState); - {check_process_code, {Tag, Pid, GC}, Res} -> - cpc_handle_cpc(NoReq, NoGcReq, GC, Pid, Res, KillState) +cpc_result(#cpc_static{purge_requests = PReqs}, NewPReqs, Res) -> + {Res, PReqs ++ cpc_reverse(NewPReqs)}. + +cpc_reverse([_] = L) -> L; +cpc_reverse(Xs) -> cpc_reverse(Xs, []). + +cpc_reverse([], Ys) -> Ys; +cpc_reverse([X|Xs], Ys) -> cpc_reverse(Xs, [X|Ys]). + +cpc_handle_down(R, #cpc_kill{outstanding = Rs, + no_outstanding = N} = KillState0) -> + try + NewOutst = cpc_list_rm(R, Rs), + KillState1 = KillState0#cpc_kill{outstanding = NewOutst, + no_outstanding = N-1}, + cpc_sched_kill_waiting(KillState1) + catch + throw : undefined -> %% Triggered by garbage message... + KillState0 end. -cpc_handle_down(NoReq, NoGcReq, R, #cpc_kill{outstanding = Rs, - no_outstanding = N} = KillState) -> - {NoReq, NoGcReq, undefined, 'DOWN', - cpc_sched_kill_waiting(KillState#cpc_kill{outstanding = cpc_list_rm(R, Rs), - no_outstanding = N-1})}. - +cpc_list_rm(_R, []) -> + throw(undefined); cpc_list_rm(R, [R|Rs]) -> Rs; cpc_list_rm(R0, [R1|Rs]) -> [R1|cpc_list_rm(R0, Rs)]. -cpc_handle_cpc(NoReq, NoGcReq, false, Pid, Res, KillState) -> - {NoReq-1, NoGcReq, Pid, Res, KillState}; -cpc_handle_cpc(NoReq, NoGcReq, true, Pid, Res, KillState) -> - {NoReq, NoGcReq-1, Pid, Res, KillState}. - cpc_sched_kill_waiting(#cpc_kill{waiting = []} = KillState) -> KillState; cpc_sched_kill_waiting(#cpc_kill{outstanding = Rs, @@ -343,18 +297,13 @@ cpc_sched_kill(Pid, no_outstanding = N+1, killed = true}. -cpc_request(#cpc_static{tag = Tag, module = Mod}, Pid, AllowGc) -> - erts_internal:check_process_code(Pid, Mod, [{async, {Tag, Pid, AllowGc}}, - {allow_gc, AllowGc}]). - -cpc_request_gc(CpcS, [Pid|Pids]) -> - cpc_request(CpcS, Pid, true), - Pids. +cpc_request(#cpc_static{tag = Tag, module = Mod}, Pid) -> + erts_internal:check_process_code(Pid, Mod, [{async, {Tag, Pid}}]). cpc_init(_CpcS, [], NoReqs) -> NoReqs; cpc_init(CpcS, [Pid|Pids], NoReqs) -> - cpc_request(CpcS, Pid, false), + cpc_request(CpcS, Pid), cpc_init(CpcS, Pids, NoReqs+1). % end of check_proc_code() implementation. @@ -366,64 +315,63 @@ cpc_init(CpcS, [Pid|Pids], NoReqs) -> %% as usual, but the tester can control when to enter the %% specific phases. %% -do_test_purge(Mod, From, Type, Ref) when Type == true; Type == false -> - Mon = erlang:monitor(process, From), - Res = case Type of - true -> do_test_hard_purge(Mod, From, Ref, Mon); - false -> do_test_soft_purge(Mod, From, Ref, Mon) - end, +do_test_purge(Mod, From, true, Ref, Reqs) -> + {Res, NewReqs} = do_test_hard_purge(Mod, From, Ref, Reqs), + From ! {test_purge, Res, Ref}, + NewReqs; +do_test_purge(Mod, From, false, Ref, Reqs) -> + {Res, NewReqs} = do_test_soft_purge(Mod, From, Ref, Reqs), From ! {test_purge, Res, Ref}, - erlang:demonitor(Mon, [flush]), - ok; -do_test_purge(_, _, _, _) -> - ok. + NewReqs; +do_test_purge(_, _, _, _, Reqs) -> + Reqs. -do_test_soft_purge(Mod, From, Ref, Mon) -> +do_test_soft_purge(Mod, From, Ref, Reqs) -> PrepRes = erts_internal:purge_module(Mod, prepare), - TestRes = test_progress(started, From, Mon, Ref, ok), + TestRes = test_progress(started, From, Ref, ok), case PrepRes of false -> - _ = test_progress(continued, From, Mon, Ref, TestRes), - true; + _ = test_progress(continued, From, Ref, TestRes), + {true, Reqs}; true -> - Res = check_proc_code(erlang:processes(), Mod, false), - _ = test_progress(continued, From, Mon, Ref, TestRes), - erts_internal:purge_module(Mod, - case Res of - false -> abort; - true -> complete - end) + {PurgeOp, NewReqs} = check_proc_code(erlang:processes(), + Mod, false, Reqs), + _ = test_progress(continued, From, Ref, TestRes), + {erts_internal:purge_module(Mod, PurgeOp), NewReqs} end. -do_test_hard_purge(Mod, From, Ref, Mon) -> +do_test_hard_purge(Mod, From, Ref, Reqs) -> PrepRes = erts_internal:purge_module(Mod, prepare), - TestRes = test_progress(started, From, Mon, Ref, ok), + TestRes = test_progress(started, From, Ref, ok), case PrepRes of false -> - _ = test_progress(continued, From, Mon, Ref, TestRes), - {false, false}; + _ = test_progress(continued, From, Ref, TestRes), + {{false, false}, Reqs}; true -> - DidKill = check_proc_code(erlang:processes(), Mod, true), - _ = test_progress(continued, From, Mon, Ref, TestRes), + {DidKill, NewReqs} = check_proc_code(erlang:processes(), + Mod, true, Reqs), + _ = test_progress(continued, From, Ref, TestRes), true = erts_internal:purge_module(Mod, complete), - {true, DidKill} + {{true, DidKill}, NewReqs} end. -test_progress(_State, _From, _Mon, _Ref, died) -> +test_progress(_State, _From, _Ref, died) -> %% Test process died; continue so we wont %% leave the system in an inconsistent %% state... died; -test_progress(started, From, Mon, Ref, ok) -> +test_progress(started, From, Ref, ok) -> From ! {started, Ref}, + Mon = erlang:monitor(process, From), receive {'DOWN', Mon, process, From, _} -> died; - {continue, Ref} -> ok + {continue, Ref} -> erlang:demonitor(Mon, [flush]), ok end; -test_progress(continued, From, Mon, Ref, ok) -> +test_progress(continued, From, Ref, ok) -> From ! {continued, Ref}, + Mon = erlang:monitor(process, From), receive {'DOWN', Mon, process, From, _} -> died; - {complete, Ref} -> ok + {complete, Ref} -> erlang:demonitor(Mon, [flush]), ok end. diff --git a/erts/preloaded/src/erts_dirty_process_code_checker.erl b/erts/preloaded/src/erts_dirty_process_code_checker.erl deleted file mode 100644 index 911642082c..0000000000 --- a/erts/preloaded/src/erts_dirty_process_code_checker.erl +++ /dev/null @@ -1,82 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%% http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% - --module(erts_dirty_process_code_checker). - --export([start/0]). - -%% -%% The erts_dirty_process_code_checker is started at -%% VM boot by the VM. It is a spawned as a system -%% process, i.e, the whole VM will terminate if -%% this process terminates. -%% -start() -> - process_flag(trap_exit, true), - msg_loop(). - -msg_loop() -> - _ = receive - Request -> - handle_request(Request) - end, - msg_loop(). - -check_process(Requester, Target, ReqId, Module) -> - Result = erts_internal:check_dirty_process_code(Target, Module), - Requester ! {check_process_code, ReqId, Result}. - -handle_request({Requester, - Target, - Prio, - {check_process_code, - ReqId, - Module, - _Flags} = Op}) -> - %% - %% Target may have stopped executing dirty since the - %% initial request was made. Check its current state - %% and try to send the request if possible; otherwise, - %% check the dirty executing process and send the result... - %% - try - case erts_internal:is_process_executing_dirty(Target) of - true -> - check_process(Requester, Target, ReqId, Module); - false -> - case erts_internal:request_system_task(Requester, - Target, - Prio, - Op) of - ok -> - ok; - dirty_execution -> - check_process(Requester, Target, ReqId, Module) - end - end - catch - _ : _ -> - ok %% Ignore all failures; someone passed us garbage... - end; -handle_request(_Garbage) -> - ignore. - - - diff --git a/erts/preloaded/src/erts_dirty_process_signal_handler.erl b/erts/preloaded/src/erts_dirty_process_signal_handler.erl new file mode 100644 index 0000000000..381f81ef14 --- /dev/null +++ b/erts/preloaded/src/erts_dirty_process_signal_handler.erl @@ -0,0 +1,103 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(erts_dirty_process_signal_handler). + +-export([start/0]). + +%% +%% The erts_dirty_process_signal_handler is started at +%% VM boot by the VM. It is a spawned as a system +%% process, i.e, the whole VM will terminate if +%% this process terminates. +%% +start() -> + process_flag(trap_exit, true), + msg_loop(). + +msg_loop() -> + _ = receive + Request -> + try + handle_request(Request) + catch + _ : _ -> + %% Ignore all failures; + %% someone passed us garbage... + ok + end + end, + msg_loop(). + +handle_request(Pid) when is_pid(Pid) -> + handle_incoming_signals(Pid, 0); +handle_request({Requester, Target, Prio, + {SysTaskOp, ReqId, Arg} = Op} = Request) -> + case handle_sys_task(Requester, Target, SysTaskOp, ReqId, Arg, 0) of + done -> + ok; + busy -> + self() ! Request; + normal -> + %% Target has stopped executing dirty since the + %% initial request was made. Dispatch the + %% request to target and let it handle it itself... + case erts_internal:request_system_task(Requester, + Target, + Prio, + Op) of + ok -> + ok; + dirty_execution -> + %% Ahh... It began executing dirty again... + handle_request(Request) + end + end; +handle_request(_Garbage) -> + ignore. + +%% +%% ---------------------------------------------------------------------------- +%% + +handle_incoming_signals(Pid, 5) -> + self() ! Pid; %% Work with other requests for a while... +handle_incoming_signals(Pid, N) -> + case erts_internal:dirty_process_handle_signals(Pid) of + more -> handle_incoming_signals(Pid, N+1); + _Res -> ok + end. + +handle_sys_task(Requester, Target, check_process_code, ReqId, Module, N) -> + case erts_internal:check_dirty_process_code(Target, Module) of + Bool when Bool == true; Bool == false -> + Requester ! {check_process_code, ReqId, Bool}, + done; + busy -> + case N > 5 of + true -> + busy; + false -> + handle_sys_task(Requester, Target, check_process_code, + ReqId, Module, N+1) + end; + Res -> + Res + end. diff --git a/erts/preloaded/src/erts_internal.erl b/erts/preloaded/src/erts_internal.erl index 6aae5ba38c..8f29a569f2 100644 --- a/erts/preloaded/src/erts_internal.erl +++ b/erts/preloaded/src/erts_internal.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012-2016. All Rights Reserved. +%% Copyright Ericsson AB 2012-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -31,7 +31,8 @@ -export([await_port_send_result/3]). -export([cmp_term/2]). --export([map_to_tuple_keys/1, term_type/1, map_hashmap_children/1]). +-export([map_to_tuple_keys/1, term_type/1, map_hashmap_children/1, + map_next/3]). -export([open_port/2, port_command/3, port_connect/2, port_close/1, port_control/3, port_call/3, port_info/1, port_info/2]). @@ -39,10 +40,13 @@ gather_system_check_result/1]). -export([request_system_task/3, request_system_task/4]). +-export([garbage_collect/1]). -export([check_process_code/3]). -export([check_dirty_process_code/2]). -export([is_process_executing_dirty/1]). +-export([dirty_process_handle_signals/1]). + -export([release_literal_area_switch/0]). -export([purge_module/2]). @@ -59,8 +63,39 @@ -export([trace/3, trace_pattern/3]). +-export([dist_ctrl_put_data/2]). + +-export([get_dflags/0]). +-export([new_connection/1]). +-export([abort_connection/2]). + +-export([scheduler_wall_time/1, system_flag_scheduler_wall_time/1, + gather_sched_wall_time_result/1, + await_sched_wall_time_modifications/2]). + +-export([group_leader/2, group_leader/3]). + %% Auto import name clash --export([check_process_code/2]). +-export([check_process_code/1]). + +-export([is_process_alive/1, is_process_alive/2]). + +-export([gather_alloc_histograms/1, gather_carrier_info/1]). + +-export([suspend_process/2]). + +-export([process_display/2]). + +-export([process_flag/3]). + +-export([create_dist_channel/4]). + +-export([erase_persistent_terms/0]). + +-export([atomics_new/2]). + +-export([counters_new/1, counters_get/2, counters_add/3, + counters_put/3, counters_info/1]). %% %% Await result of send to port @@ -205,8 +240,9 @@ port_info(_Result, _Item) -> -spec request_system_task(Pid, Prio, Request) -> 'ok' when Prio :: 'max' | 'high' | 'normal' | 'low', - Request :: {'garbage_collect', term()} - | {'check_process_code', term(), module(), non_neg_integer()} + Type :: 'major' | 'minor', + Request :: {'garbage_collect', term(), Type} + | {'check_process_code', term(), module()} | {'copy_literals', term(), boolean()}, Pid :: pid(). @@ -216,7 +252,7 @@ request_system_task(_Pid, _Prio, _Request) -> -spec request_system_task(RequesterPid, TargetPid, Prio, Request) -> 'ok' | 'dirty_execution' when Prio :: 'max' | 'high' | 'normal' | 'low', Request :: {'garbage_collect', term()} - | {'check_process_code', term(), module(), non_neg_integer()} + | {'check_process_code', term(), module()} | {'copy_literals', term(), boolean()}, RequesterPid :: pid(), TargetPid :: pid(). @@ -224,12 +260,14 @@ request_system_task(_Pid, _Prio, _Request) -> request_system_task(_RequesterPid, _TargetPid, _Prio, _Request) -> erlang:nif_error(undefined). --define(ERTS_CPC_ALLOW_GC, (1 bsl 0)). +-spec garbage_collect(Mode) -> 'true' when Mode :: 'major' | 'minor'. --spec check_process_code(Module, Flags) -> boolean() when - Module :: module(), - Flags :: non_neg_integer(). -check_process_code(_Module, _Flags) -> +garbage_collect(_Mode) -> + erlang:nif_error(undefined). + +-spec check_process_code(Module) -> boolean() when + Module :: module(). +check_process_code(_Module) -> erlang:nif_error(undefined). -spec check_process_code(Pid, Module, OptionList) -> CheckResult | async when @@ -240,7 +278,7 @@ check_process_code(_Module, _Flags) -> OptionList :: [Option], CheckResult :: boolean() | aborted. check_process_code(Pid, Module, OptionList) -> - {Async, Flags} = get_cpc_opts(OptionList, sync, ?ERTS_CPC_ALLOW_GC), + Async = get_cpc_opts(OptionList, sync), case Async of {async, ReqId} -> {priority, Prio} = erlang:process_info(erlang:self(), @@ -249,13 +287,12 @@ check_process_code(Pid, Module, OptionList) -> Prio, {check_process_code, ReqId, - Module, - Flags}), + Module}), async; sync -> case Pid == erlang:self() of true -> - erts_internal:check_process_code(Module, Flags); + erts_internal:check_process_code(Module); false -> {priority, Prio} = erlang:process_info(erlang:self(), priority), @@ -264,8 +301,7 @@ check_process_code(Pid, Module, OptionList) -> Prio, {check_process_code, ReqId, - Module, - Flags}), + Module}), receive {check_process_code, ReqId, CheckResult} -> CheckResult @@ -273,20 +309,17 @@ check_process_code(Pid, Module, OptionList) -> end end. -% gets async and flag opts and verify valid option list -get_cpc_opts([{async, _ReqId} = AsyncTuple | Options], _OldAsync, Flags) -> - get_cpc_opts(Options, AsyncTuple, Flags); -get_cpc_opts([{allow_gc, AllowGC} | Options], Async, Flags) -> - get_cpc_opts(Options, Async, cpc_flags(Flags, ?ERTS_CPC_ALLOW_GC, AllowGC)); -get_cpc_opts([], Async, Flags) -> - {Async, Flags}. - -cpc_flags(OldFlags, Bit, true) -> - OldFlags bor Bit; -cpc_flags(OldFlags, Bit, false) -> - OldFlags band (bnot Bit). - --spec check_dirty_process_code(Pid,Module) -> 'true' | 'false' when +% gets async opt and verify valid option list +get_cpc_opts([{async, _ReqId} = AsyncTuple | Options], _OldAsync) -> + get_cpc_opts(Options, AsyncTuple); +get_cpc_opts([{allow_gc, AllowGC} | Options], Async) when AllowGC == true; + AllowGC == false -> + get_cpc_opts(Options, Async); +get_cpc_opts([], Async) -> + Async. + +-spec check_dirty_process_code(Pid, Module) -> Result when + Result :: boolean() | 'normal' | 'busy', Pid :: pid(), Module :: module(). check_dirty_process_code(_Pid,_Module) -> @@ -297,6 +330,13 @@ check_dirty_process_code(_Pid,_Module) -> is_process_executing_dirty(_Pid) -> erlang:nif_error(undefined). +-spec dirty_process_handle_signals(Pid) -> Res when + Pid :: pid(), + Res :: 'false' | 'true' | 'noproc' | 'normal' | 'more' | 'ok'. + +dirty_process_handle_signals(_Pid) -> + erlang:nif_error(undefined). + -spec release_literal_area_switch() -> 'true' | 'false'. release_literal_area_switch() -> @@ -366,6 +406,18 @@ term_type(_T) -> map_hashmap_children(_M) -> erlang:nif_error(undefined). +%% return the next assoc in the iterator and a new iterator +-spec map_next(I, M, A) -> {K,V,NI} | list() when + I :: non_neg_integer(), + M :: map(), + K :: term(), + V :: term(), + A :: iterator | list(), + NI :: maps:iterator(). + +map_next(_I, _M, _A) -> + erlang:nif_error(undefined). + -spec erts_internal:flush_monitor_messages(Ref, Multi, Res) -> term() when Ref :: reference(), Multi :: boolean(), @@ -438,10 +490,11 @@ microstate_accounting(Ref, Threads) -> trace(_PidSpec, _How, _FlagList) -> erlang:nif_error(undefined). +-type match_variable() :: atom(). % Approximation of '$1' | '$2' | ... -type trace_pattern_mfa() :: {atom(),atom(),arity() | '_'} | on_load. -type trace_match_spec() :: - [{[term()] | '_' ,[term()],[term()]}]. + [{[term()] | '_' | match_variable() ,[term()],[term()]}]. -spec trace_pattern(MFA, MatchSpec, FlagList) -> non_neg_integer() when MFA :: trace_pattern_mfa(), @@ -452,3 +505,224 @@ trace(_PidSpec, _How, _FlagList) -> FlagList :: [ ]. trace_pattern(_MFA, _MatchSpec, _FlagList) -> erlang:nif_error(undefined). + +-spec dist_ctrl_put_data(DHandle, Data) -> 'ok' when + DHandle :: erlang:dist_handle(), + Data :: iolist(). + +dist_ctrl_put_data(DHandle, IoList) -> + %% + %% Helper for erlang:dist_ctrl_put_data/2 + %% + %% erlang:dist_ctrl_put_data/2 traps to + %% this function if second argument is + %% a list... + %% + try + Binary = erlang:iolist_to_binary(IoList), + %% Restart erlang:dist_ctrl_put_data/2 + %% with the iolist converted to a binary... + erlang:dist_ctrl_put_data(DHandle, Binary) + catch + Class : Reason -> + %% Throw exception as if thrown from + %% erlang:dist_ctrl_put_data/2 ... + RootST = try erlang:error(Reason) + catch + error:Reason:ST -> + case ST of + [] -> []; + [_|T] -> T + end + end, + StackTrace = [{erlang, dist_ctrl_put_data, + [DHandle, IoList], []} + | RootST], + erlang:raise(Class, Reason, StackTrace) + end. + + +-spec erts_internal:get_dflags() -> {erts_dflags, integer(), integer(), + integer(), integer(), integer()}. +get_dflags() -> + erlang:nif_error(undefined). + +-spec erts_internal:new_connection(Node) -> ConnId when + Node :: atom(), + ConnId :: {integer(), erlang:dist_handle()}. +new_connection(_Node) -> + erlang:nif_error(undefined). + +-spec erts_internal:abort_connection(Node, ConnId) -> boolean() when + Node :: atom(), + ConnId :: {integer(), erlang:dist_handle()}. +abort_connection(_Node, _ConnId) -> + erlang:nif_error(undefined). + +%% Scheduler wall time + +-spec erts_internal:system_flag_scheduler_wall_time(Enable) -> boolean() when + Enable :: boolean(). + +system_flag_scheduler_wall_time(Bool) -> + kernel_refc:scheduler_wall_time(Bool). + + +-spec erts_internal:await_sched_wall_time_modifications(Ref, Result) -> boolean() when + Ref :: reference(), + Result :: boolean(). + +-spec erts_internal:scheduler_wall_time(Enable) -> boolean() when + Enable :: boolean(). + +scheduler_wall_time(_Enable) -> + erlang:nif_error(undefined). + +await_sched_wall_time_modifications(Ref, Result) -> + sched_wall_time(Ref, erlang:system_info(schedulers)), + Result. + +-spec erts_internal:gather_sched_wall_time_result(Ref) -> [{pos_integer(), + non_neg_integer(), + non_neg_integer()}] when + Ref :: reference(). + +gather_sched_wall_time_result(Ref) when erlang:is_reference(Ref) -> + sched_wall_time(Ref, erlang:system_info(schedulers), []). + +sched_wall_time(_Ref, 0) -> + ok; +sched_wall_time(Ref, N) -> + receive Ref -> sched_wall_time(Ref, N-1) end. + +sched_wall_time(_Ref, 0, Acc) -> + Acc; +sched_wall_time(Ref, N, undefined) -> + receive {Ref, _} -> sched_wall_time(Ref, N-1, undefined) end; +sched_wall_time(Ref, N, Acc) -> + receive + {Ref, undefined} -> sched_wall_time(Ref, N-1, undefined); + {Ref, SWTL} when erlang:is_list(SWTL) -> sched_wall_time(Ref, N-1, Acc ++ SWTL); + {Ref, SWT} -> sched_wall_time(Ref, N-1, [SWT|Acc]) + end. + +-spec erts_internal:group_leader(GL, Pid) -> true | false | badarg when + GL :: pid(), + Pid :: pid(). + +group_leader(_GL, _Pid) -> + erlang:nif_error(undefined). + +-spec erts_internal:group_leader(GL, Pid, Ref) -> ok when + GL :: pid(), + Pid :: pid(), + Ref :: reference(). + +group_leader(_GL, _Pid, _Ref) -> + erlang:nif_error(undefined). + +-spec erts_internal:is_process_alive(Pid, Ref) -> 'ok' when + Pid :: pid(), + Ref :: reference(). + +is_process_alive(_Pid, _Ref) -> + erlang:nif_error(undefined). + +-spec erts_internal:is_process_alive(Pid) -> boolean() when + Pid :: pid(). + +is_process_alive(Pid) -> + Ref = make_ref(), + erts_internal:is_process_alive(Pid, Ref), + receive + {Ref, Res} -> + Res + end. + +-spec gather_alloc_histograms({Type, SchedId, HistWidth, HistStart, Ref}) -> MsgCount when + Type :: atom(), + SchedId :: non_neg_integer(), + HistWidth :: non_neg_integer(), + HistStart :: non_neg_integer(), + Ref :: reference(), + MsgCount :: non_neg_integer(). + +gather_alloc_histograms(_) -> + erlang:nif_error(undef). + +-spec gather_carrier_info({Type, SchedId, HistWidth, HistStart, Ref}) -> MsgCount when + Type :: atom(), + SchedId :: non_neg_integer(), + HistWidth :: non_neg_integer(), + HistStart :: non_neg_integer(), + Ref :: reference(), + MsgCount :: non_neg_integer(). + +gather_carrier_info(_) -> + erlang:nif_error(undef). + +-spec suspend_process(Suspendee, OptList) -> Result when + Result :: boolean() | 'badarg' | reference(), + Suspendee :: pid(), + OptList :: [Opt], + Opt :: unless_suspending | asynchronous | {asynchronous, term()}. + +suspend_process(_Suspendee, _OptList) -> + erlang:nif_error(undefined). + +%% process_display/2 +-spec process_display(Pid, Type) -> 'true' | 'badarg' | reference() when + Pid :: pid(), + Type :: backtrace. +process_display(_Pid, _Type) -> + erlang:nif_error(undefined). + +%% process_flag/3 +-spec process_flag(Pid, Flag, Value) -> OldValue | 'badarg' | reference() when + Pid :: pid(), + Flag :: save_calls, + Value :: non_neg_integer(), + OldValue :: non_neg_integer(). +process_flag(_Pid, _Flag, _Value) -> + erlang:nif_error(undefined). + +-spec create_dist_channel(Node, DistCtrlr, Flags, Ver) -> Result when + Node :: atom(), + DistCtrlr :: port() | pid(), + Flags :: integer(), + Ver :: integer(), + Result :: {'ok', erlang:dist_handle()} + | {'message', reference()} + | 'badarg' + | 'system_limit'. + +create_dist_channel(_Node, _DistCtrlr, _Flags, _Ver) -> + erlang:nif_error(undefined). + +-spec erase_persistent_terms() -> 'ok'. +erase_persistent_terms() -> + erlang:nif_error(undefined). + +-spec atomics_new(pos_integer(), pos_integer()) -> reference(). +atomics_new(_Arity, _EncOpts) -> + erlang:nif_error(undef). + +-spec counters_new(pos_integer()) -> reference(). +counters_new(_Size) -> + erlang:nif_error(undef). + +-spec counters_get(reference(), pos_integer()) -> integer(). +counters_get(_Ref, _Ix) -> + erlang:nif_error(undef). + +-spec counters_add(reference(), pos_integer(), integer()) -> ok. +counters_add(_Ref, _Ix, _Incr) -> + erlang:nif_error(undef). + +-spec counters_put(reference(), pos_integer(), integer()) -> ok. +counters_put(_Ref, _Ix, _Value) -> + erlang:nif_error(undef). + +-spec counters_info(reference()) -> #{}. +counters_info(_Ref) -> + erlang:nif_error(undef). diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index 962528f7ab..b4b8b3bf9b 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2016. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -32,8 +32,8 @@ %% (Optional - default efile) %% -hosts [Node] : List of hosts from which we can boot. %% (Mandatory if -loader inet) -%% -mode embedded : Load all modules at startup, no automatic loading -%% -mode interactive : Auto load modules (default system behaviour). +%% -mode interactive : Auto load modules not needed at startup (default system behaviour). +%% -mode embedded : Load all modules in the boot script, disable auto loading. %% -path : Override path in bootfile. %% -pa Path+ : Add my own paths first. %% -pz Path+ : Add my own paths last. @@ -200,12 +200,15 @@ boot(BootArgs) -> register(init, self()), process_flag(trap_exit, true), - %% Load the tracer nif + %% Load the static nifs + zlib:on_load(), erl_tracer:on_load(), + prim_buffer:on_load(), + prim_file:on_load(), {Start0,Flags,Args} = parse_boot_args(BootArgs), %% We don't get to profile parsing of BootArgs - case get_flag(profile_boot, Flags, false) of + case b2a(get_flag(profile_boot, Flags, false)) of false -> ok; true -> debug_profile_start() end, @@ -263,21 +266,30 @@ boot(Start,Flags,Args) -> boot_loop(BootPid,State). %%% Convert a term to a printable string, if possible. -to_string(X) when is_list(X) -> % assume string +to_string(X, D) when is_list(X), D < 4 -> % assume string F = flatten(X, []), case printable_list(F) of - true -> F; - false -> "" + true when length(F) > 0 -> F; + _false -> + List = [to_string(E, D+1) || E <- X], + flatten(["[",join(List),"]"], []) end; -to_string(X) when is_atom(X) -> +to_string(X, _D) when is_list(X) -> + "[_]"; +to_string(X, _D) when is_atom(X) -> atom_to_list(X); -to_string(X) when is_pid(X) -> +to_string(X, _D) when is_pid(X) -> pid_to_list(X); -to_string(X) when is_float(X) -> +to_string(X, _D) when is_float(X) -> float_to_list(X); -to_string(X) when is_integer(X) -> +to_string(X, _D) when is_integer(X) -> integer_to_list(X); -to_string(_X) -> +to_string(X, D) when is_tuple(X), D < 4 -> + List = [to_string(E, D+1) || E <- tuple_to_list(X)], + flatten(["{",join(List),"}"], []); +to_string(X, _D) when is_tuple(X) -> + "{_}"; +to_string(_X, _D) -> "". % can't do anything with it %% This is an incorrect and narrow definition of printable characters. @@ -291,6 +303,13 @@ printable_list([$\t|T]) -> printable_list(T); printable_list([]) -> true; printable_list(_) -> false. +join([] = T) -> + T; +join([_Elem] = T) -> + T; +join([Elem|T]) -> + [Elem,","|join(T)]. + flatten([H|T], Tail) when is_list(H) -> flatten(H, flatten(T, Tail)); flatten([H|T], Tail) -> @@ -299,7 +318,7 @@ flatten([], Tail) -> Tail. things_to_string([X|Rest]) -> - " (" ++ to_string(X) ++ ")" ++ things_to_string(Rest); + " (" ++ to_string(X, 0) ++ ")" ++ things_to_string(Rest); things_to_string([]) -> "". @@ -307,9 +326,8 @@ halt_string(String, List) -> String ++ things_to_string(List). %% String = string() -%% List = [string() | atom() | pid() | number()] -%% Any other items in List, such as tuples, are ignored when creating -%% the string used as argument to erlang:halt/1. +%% List = [string() | atom() | pid() | number() | list() | tuple()] +%% Items in List are truncated if found to be too large -spec crash(_, _) -> no_return(). crash(String, List) -> halt(halt_string(String, List)). @@ -467,7 +485,12 @@ do_handle_msg(Msg,State) -> X -> case whereis(user) of undefined -> - catch error_logger ! {info, self(), {self(), X, []}}; + Time = erlang:monotonic_time(microsecond), + catch logger ! {log, info, "init got unexpected: ~p", [X], + #{pid=>self(), + gl=>self(), + time=>Time, + error_logger=>#{tag=>info_msg}}}; User -> User ! X, ok @@ -527,6 +550,9 @@ stop(Reason,State) -> do_stop(Reason,State1). do_stop(restart,#state{start = Start, flags = Flags, args = Args}) -> + %% Make sure we don't have any outstanding messages before doing the restart. + flush(), + erts_internal:erase_persistent_terms(), boot(Start,Flags,Args); do_stop(reboot,_) -> halt(); @@ -539,8 +565,18 @@ do_stop({stop,Status},State) -> clear_system(BootPid,State) -> Heart = get_heart(State#state.kernel), - shutdown_pids(Heart,BootPid,State), - unload(Heart). + Logger = get_logger(State#state.kernel), + shutdown_pids(Heart,Logger,BootPid,State), + unload(Heart), + kill_em([Logger]), + do_unload([logger_server]). + +flush() -> + receive + _M -> flush() + after 0 -> + ok + end. stop_heart(State) -> case get_heart(State#state.kernel) of @@ -553,19 +589,26 @@ stop_heart(State) -> shutdown_kernel_pid(Pid, BootPid, self(), State) end. -shutdown_pids(Heart,BootPid,State) -> +shutdown_pids(Heart,Logger,BootPid,State) -> Timer = shutdown_timer(State#state.flags), catch shutdown(State#state.kernel,BootPid,Timer,State), - kill_all_pids(Heart), % Even the shutdown timer. - kill_all_ports(Heart), + kill_all_pids(Heart,Logger), % Even the shutdown timer. + kill_all_ports(Heart), % Logger has no ports flush_timout(Timer). -get_heart([{heart,Pid}|_Kernel]) -> Pid; -get_heart([_|Kernel]) -> get_heart(Kernel); -get_heart(_) -> false. +get_heart(Kernel) -> + get_kernelpid(heart,Kernel). +get_logger(Kernel) -> + get_kernelpid(logger,Kernel). -shutdown([{heart,_Pid}|Kernel],BootPid,Timer,State) -> +get_kernelpid(Name,[{Name,Pid}|_Kernel]) -> Pid; +get_kernelpid(Name,[_|Kernel]) -> get_kernelpid(Name,Kernel); +get_kernelpid(_,_) -> false. + + +shutdown([{Except,_Pid}|Kernel],BootPid,Timer,State) + when Except==heart; Except==logger -> shutdown(Kernel, BootPid, Timer, State); shutdown([{_Name,Pid}|Kernel],BootPid,Timer,State) -> shutdown_kernel_pid(Pid, BootPid, Timer, State), @@ -617,24 +660,25 @@ resend(_) -> %% %% Kill all existing pids in the system (except init and heart). -kill_all_pids(Heart) -> - case get_pids(Heart) of +kill_all_pids(Heart,Logger) -> + case get_pids(Heart,Logger) of [] -> ok; Pids -> kill_em(Pids), - kill_all_pids(Heart) % Continue until all are really killed. + kill_all_pids(Heart,Logger) % Continue until all are really killed. end. %% All except system processes. -get_pids(Heart) -> +get_pids(Heart,Logger) -> Pids = [P || P <- processes(), not erts_internal:is_system_process(P)], - delete(Heart,self(),Pids). + delete(Heart,Logger,self(),Pids). -delete(Heart,Init,[Heart|Pids]) -> delete(Heart,Init,Pids); -delete(Heart,Init,[Init|Pids]) -> delete(Heart,Init,Pids); -delete(Heart,Init,[Pid|Pids]) -> [Pid|delete(Heart,Init,Pids)]; -delete(_,_,[]) -> []. +delete(Heart,Logger,Init,[Heart|Pids]) -> delete(Heart,Logger,Init,Pids); +delete(Heart,Logger,Init,[Logger|Pids]) -> delete(Heart,Logger,Init,Pids); +delete(Heart,Logger,Init,[Init|Pids]) -> delete(Heart,Logger,Init,Pids); +delete(Heart,Logger,Init,[Pid|Pids]) -> [Pid|delete(Heart,Logger,Init,Pids)]; +delete(_,_,_,[]) -> []. kill_em([Pid|Pids]) -> exit(Pid,kill), @@ -664,9 +708,9 @@ kill_all_ports(_,_) -> ok. unload(false) -> - do_unload(sub(erlang:pre_loaded(),erlang:loaded())); + do_unload(sub([logger_server|erlang:pre_loaded()],erlang:loaded())); unload(_) -> - do_unload(sub([heart|erlang:pre_loaded()],erlang:loaded())). + do_unload(sub([heart,logger_server|erlang:pre_loaded()],erlang:loaded())). do_unload([M|Mods]) -> catch erlang:purge_module(M), @@ -674,16 +718,8 @@ do_unload([M|Mods]) -> catch erlang:purge_module(M), do_unload(Mods); do_unload([]) -> - purge_all_hipe_refs(), ok. -purge_all_hipe_refs() -> - case erlang:system_info(hipe_architecture) of - undefined -> ok; - _ -> hipe_bifs:remove_refs_from(all) - end. - - sub([H|T],L) -> sub(T,del(H,L)); sub([],L) -> L. @@ -782,7 +818,7 @@ do_boot(Init,Flags,Start) -> (catch erlang:system_info({purify, "Node: " ++ atom_to_list(node())})), start_em(Start), - case get_flag(profile_boot,Flags,false) of + case b2a(get_flag(profile_boot,Flags,false)) of false -> ok; true -> debug_profile_format_mfas(debug_profile_mfas()), @@ -932,15 +968,15 @@ load_rest([], _) -> prepare_loading_fun() -> fun(Mod, FullName, Beam) -> case erlang:prepare_loading(Mod, Beam) of - Prepared when is_binary(Prepared) -> + {error,_}=Error -> + Error; + Prepared -> case erlang:has_prepared_code_on_load(Prepared) of true -> {ok,{on_load,Beam,FullName}}; false -> {ok,{prepared,Prepared,FullName}} - end; - {error,_}=Error -> - Error + end end end. @@ -1084,7 +1120,7 @@ start_it({eval,Bin}) -> {ok,Ts,_} = erl_scan:string(Str), Ts1 = case reverse(Ts) of [{dot,_}|_] -> Ts; - TsR -> reverse([{dot,1} | TsR]) + TsR -> reverse([{dot,erl_anno:new(1)} | TsR]) end, {ok,Expr} = erl_parse:parse_exprs(Ts1), {value, _Value, _Bs} = erl_eval:exprs(Expr, erl_eval:new_bindings()), diff --git a/erts/preloaded/src/persistent_term.erl b/erts/preloaded/src/persistent_term.erl new file mode 100644 index 0000000000..5d0c266127 --- /dev/null +++ b/erts/preloaded/src/persistent_term.erl @@ -0,0 +1,55 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(persistent_term). + +-export([erase/1,get/0,get/1,info/0,put/2]). + +-type key() :: term(). +-type value() :: term(). + +-spec erase(Key) -> Result when + Key :: key(), + Result :: boolean(). +erase(_Key) -> + erlang:nif_error(undef). + +-spec get() -> List when + List :: [{key(),value()}]. +get() -> + erlang:nif_error(undef). + +-spec get(Key) -> Value when + Key :: key(), + Value :: value(). +get(_Key) -> + erlang:nif_error(undef). + +-spec info() -> Info when + Info :: #{'count':=Count,'memory':=Memory}, + Count :: non_neg_integer(), + Memory :: non_neg_integer(). +info() -> + erlang:nif_error(undef). + +-spec put(Key, Value) -> 'ok' when + Key :: key(), + Value :: value(). +put(_Key, _Value) -> + erlang:nif_error(undef). diff --git a/erts/preloaded/src/prim_buffer.erl b/erts/preloaded/src/prim_buffer.erl new file mode 100644 index 0000000000..e0d35a6792 --- /dev/null +++ b/erts/preloaded/src/prim_buffer.erl @@ -0,0 +1,140 @@ +%% +%% %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(prim_buffer). + +-export([on_load/0]). + +%% This is a mutable binary buffer that helps break out buffering logic from +%% NIFs/drivers, which is often the only thing that prevents the C code from +%% being reduced to bare system call wrappers. +%% +%% All operations in this file are thread-unsafe and risk crashing the emulator +%% if you're not careful. + +-export([new/0, size/1, wipe/1, read/2, read_iovec/2, write/2, skip/2]). + +-export([find_byte_index/2]). + +-export([try_lock/1, unlock/1]). + +-type prim_buffer() :: term(). + +%% Controls when to copy rather than extract sub-binaries from the buffer, +%% reducing the risk of small reads keeping a large binary alive. +-define(COPYING_READ_LIMIT, 512). + +%% Reads that fit into heap binaries are always copied since the cost of +%% peeking binaries that short is largely equivalent to copying. +-define(ERL_ONHEAP_BIN_LIMIT, 64). + +on_load() -> + case erlang:load_nif(atom_to_list(?MODULE), 0) of + ok -> ok + end. + +-spec new() -> prim_buffer(). +new() -> + erlang:nif_error(undef). + +-spec size(Buffer :: prim_buffer()) -> non_neg_integer(). +size(_Buffer) -> + erlang:nif_error(undef). + +%% Reads data as a binary from the front of the buffer. This will almost always +%% result in copying so it should be avoided unless you absolutely must have a +%% binary. +-spec read(Buffer :: prim_buffer(), Size :: non_neg_integer()) -> binary(). +read(Buffer, Size) when Size =< ?ERL_ONHEAP_BIN_LIMIT -> + copying_read(Buffer, Size); +read(Buffer, Size) when Size > ?ERL_ONHEAP_BIN_LIMIT -> + iolist_to_binary(read_iovec(Buffer, Size)). + +%% Reads data as an erlang:iovec() binary from the front of the buffer, +%% avoiding copying if reasonable. +-spec read_iovec(Buffer, Size) -> IOVec when + Buffer :: prim_buffer(), + Size :: non_neg_integer(), + IOVec :: erlang:iovec(). +read_iovec(Buffer, Size) when Size =< ?ERL_ONHEAP_BIN_LIMIT -> + [copying_read(Buffer, Size)]; +read_iovec(Buffer, Size) when Size > ?ERL_ONHEAP_BIN_LIMIT -> + Head = peek_head(Buffer), + HeadSize = byte_size(Head), + if + (HeadSize - Size) > ?COPYING_READ_LIMIT, Size =< ?COPYING_READ_LIMIT -> + [copying_read(Buffer, Size)]; + HeadSize > Size -> + skip(Buffer, Size), + {First, _Rest} = split_binary(Head, Size), + [First]; + HeadSize < Size -> + skip(Buffer, HeadSize), + [Head | read_iovec(Buffer, Size - HeadSize)]; + HeadSize =:= Size -> + skip(Buffer, Size), + [Head] + end. + +%% Writes an erlang:iovec() to the back of the buffer. +-spec write(Buffer :: prim_buffer(), IOVec :: erlang:iovec()) -> ok. +write(_Buffer, _IOVec) -> + erlang:nif_error(undef). + +%% Removes data from the front of the buffer without reading it. +-spec skip(Buffer :: prim_buffer(), Size :: non_neg_integer()) -> ok. +skip(_Buffer, _Size) -> + erlang:nif_error(undef). + +-spec wipe(Buffer :: prim_buffer()) -> ok. +wipe(Buffer) -> + skip(Buffer, prim_buffer:size(Buffer)). + +%% Finds the start-index of the first occurence of Needle, for implementing +%% read_line and similar. +-spec find_byte_index(Buffer, Needle) -> Result when + Buffer :: prim_buffer(), + Needle :: non_neg_integer(), + Result :: {ok, non_neg_integer()} | + not_found. +find_byte_index(_Buffer, _Needle) -> + erlang:nif_error(undef). + +%% Attempts to take a unique lock on the buffer. Failure handling is left to +%% the user. +-spec try_lock(Buffer :: prim_buffer()) -> acquired | busy. +try_lock(_Buffer) -> + erlang:nif_error(undef). + +-spec unlock(Buffer :: prim_buffer()) -> ok. +unlock(_Buffer) -> + erlang:nif_error(undef). + +%% Unexported helper functions: + +%% Reads data from the front of the buffer, returning a copy of the data to +%% avoid holding references. +-spec copying_read(Buffer :: prim_buffer(), Size :: non_neg_integer()) -> binary(). +copying_read(_Buffer, _Size) -> + erlang:nif_error(undef). + +%% Returns the binary at the front of the buffer without modifying the buffer. +-spec peek_head(Buffer :: prim_buffer()) -> binary(). +peek_head(_Buffer) -> + erlang:nif_error(undef). diff --git a/erts/preloaded/src/prim_eval.S b/erts/preloaded/src/prim_eval.S index e7f09a870c..e4b1560517 100644 --- a/erts/preloaded/src/prim_eval.S +++ b/erts/preloaded/src/prim_eval.S @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% Copyright Ericsson AB 2013-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. @@ -26,7 +26,7 @@ {attributes, []}. -{labels, 10}. +{labels, 14}. {function, 'receive', 2, 2}. @@ -36,6 +36,9 @@ {allocate,2,2}. {move,{x,1},{y,0}}. {move,{x,0},{y,1}}. + %% Call arg_reg_alloc() in order to ensure + %% that def_arg_reg[0] isn't clobbered + {call,0,{f,7}}. {label,3}. {loop_rec,{f,5},{x,0}}. {move,{y,1},{x,1}}. @@ -53,19 +56,43 @@ {deallocate,2}. return. - -{function, module_info, 0, 8}. +{function, arg_reg_alloc, 0, 7}. {label,6}. - {func_info,{atom,prim_eval},{atom,module_info},0}. + {func_info,{atom,prim_eval},{atom,arg_reg_alloc},0}. {label,7}. + {allocate,0,0}. + {move,{integer,134217727},{x,0}}. + {call_ext,1,{extfunc,erlang,bump_reductions,1}}. + {move,{atom,true},{x,3}}. + {move,{atom,true},{x,4}}. + {move,{atom,true},{x,2}}. + {move,{atom,true},{x,5}}. + {move,{atom,true},{x,1}}. + {move,{atom,true},{x,6}}. + {move,{atom,true},{x,0}}. + {call_last,7,{f,9},0}. + + +{function, arg_reg_alloc, 7, 9}. + {label,8}. + {func_info,{atom,prim_eval},{atom,arg_reg_alloc},7}. + {label,9}. + {move,{atom,ok},{x,0}}. + return. + + +{function, module_info, 0, 11}. + {label,10}. + {func_info,{atom,prim_eval},{atom,module_info},0}. + {label,11}. {move,{atom,prim_eval},{x,0}}. {call_ext_only,1,{extfunc,erlang,get_module_info,1}}. -{function, module_info, 1, 10}. - {label,8}. +{function, module_info, 1, 13}. + {label,12}. {func_info,{atom,prim_eval},{atom,module_info},1}. - {label,9}. + {label,13}. {move,{x,0},{x,1}}. {move,{atom,prim_eval},{x,0}}. {call_ext_only,2,{extfunc,erlang,get_module_info,2}}. diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index ab5359ebbc..5fc22bc582 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -19,147 +19,45 @@ %% -module(prim_file). -%% Interface module to the file driver. +-export([on_load/0]). +-export([open/2, close/1, + sync/1, datasync/1, truncate/1, advise/4, allocate/3, + read_line/1, read/2, write/2, position/2, + pread/2, pread/3, pwrite/2, pwrite/3]). +%% OTP internal. +-export([ipread_s32bu_p32bu/3, sendfile/8, altname/1, get_handle/1]). -%%% Interface towards a single file's contents. Uses ?FD_DRV. +-export([read_file/1, write_file/2]). -%% Generic file contents operations --export([open/2, close/1, datasync/1, sync/1, advise/4, position/2, truncate/1, - write/2, pwrite/2, pwrite/3, read/2, read_line/1, pread/2, pread/3, - copy/3, sendfile/8, allocate/3]). +-export([read_link/1, read_link_all/1, + read_link_info/1, read_link_info/2, + read_file_info/1, read_file_info/2, + write_file_info/2, write_file_info/3]). -%% Specialized file operations --export([open/1, open/3]). --export([read_file/1, read_file/2, write_file/2]). --export([ipread_s32bu_p32bu/3]). +-export([list_dir/1, list_dir_all/1]). +-export([get_cwd/0, get_cwd/1, set_cwd/1, + delete/1, rename/2, + make_dir/1, del_dir/1, + make_link/2, make_symlink/2]). - -%%% Interface towards file system and metadata. Uses ?DRV. - -%% Takes an optional port (opens a ?DRV port per default) as first argument. --export([get_cwd/0, get_cwd/1, get_cwd/2, - set_cwd/1, set_cwd/2, - delete/1, delete/2, - rename/2, rename/3, - make_dir/1, make_dir/2, - del_dir/1, del_dir/2, - read_file_info/1, read_file_info/2, read_file_info/3, - altname/1, altname/2, - write_file_info/2, write_file_info/3, write_file_info/4, - make_link/2, make_link/3, - make_symlink/2, make_symlink/3, - read_link/1, read_link/2, read_link_all/1, read_link_all/2, - read_link_info/1, read_link_info/2, read_link_info/3, - list_dir/1, list_dir/2, list_dir_all/1, list_dir_all/2]). -%% How to start and stop the ?DRV port. --export([start/0, stop/1]). - -%% Debug exports --export([open_int/4, open_mode/1, open_mode/4]). - -%%%----------------------------------------------------------------- -%%% Includes and defines - --include("file.hrl"). - --define(DRV, "efile"). --define(FD_DRV, "efile"). - +-define(MIN_READLINE_SIZE, 256). -define(LARGEFILESIZE, (1 bsl 63)). -%% Driver commands --define(FILE_OPEN, 1). --define(FILE_READ, 2). --define(FILE_LSEEK, 3). --define(FILE_WRITE, 4). --define(FILE_FSTAT, 5). --define(FILE_PWD, 6). --define(FILE_READDIR, 7). --define(FILE_CHDIR, 8). --define(FILE_FSYNC, 9). --define(FILE_MKDIR, 10). --define(FILE_DELETE, 11). --define(FILE_RENAME, 12). --define(FILE_RMDIR, 13). --define(FILE_TRUNCATE, 14). --define(FILE_READ_FILE, 15). --define(FILE_WRITE_INFO, 16). --define(FILE_LSTAT, 19). --define(FILE_READLINK, 20). --define(FILE_LINK, 21). --define(FILE_SYMLINK, 22). --define(FILE_CLOSE, 23). --define(FILE_PWRITEV, 24). --define(FILE_PREADV, 25). --define(FILE_SETOPT, 26). --define(FILE_IPREAD, 27). --define(FILE_ALTNAME, 28). --define(FILE_READ_LINE, 29). --define(FILE_FDATASYNC, 30). --define(FILE_ADVISE, 31). --define(FILE_SENDFILE, 32). --define(FILE_ALLOCATE, 33). - -%% Driver responses --define(FILE_RESP_OK, 0). --define(FILE_RESP_ERROR, 1). --define(FILE_RESP_DATA, 2). --define(FILE_RESP_NUMBER, 3). --define(FILE_RESP_INFO, 4). --define(FILE_RESP_NUMERR, 5). --define(FILE_RESP_LDATA, 6). --define(FILE_RESP_N2DATA, 7). --define(FILE_RESP_EOF, 8). --define(FILE_RESP_FNAME, 9). --define(FILE_RESP_ALL_DATA, 10). --define(FILE_RESP_LFNAME, 11). - -%% Open modes for the driver's open function. --define(EFILE_MODE_READ, 1). --define(EFILE_MODE_WRITE, 2). --define(EFILE_MODE_READ_WRITE, 3). --define(EFILE_MODE_APPEND, 4). --define(EFILE_COMPRESSED, 8). --define(EFILE_MODE_EXCL, 16). -%% Note: bit 5 (32) is used internally for VxWorks --define(EFILE_MODE_SYNC, 64). - -%% Use this mask to get just the mode bits to be passed to the driver. --define(EFILE_MODE_MASK, 127). - -%% Seek modes for the driver's seek function. --define(EFILE_SEEK_SET, 0). --define(EFILE_SEEK_CUR, 1). --define(EFILE_SEEK_END, 2). - -%% Options --define(FILE_OPT_DELAYED_WRITE, 0). --define(FILE_OPT_READ_AHEAD, 1). - -%% IPREAD variants --define(IPREAD_S32BU_P32BU, 0). - -%% POSIX file advises --define(POSIX_FADV_NORMAL, 0). --define(POSIX_FADV_RANDOM, 1). --define(POSIX_FADV_SEQUENTIAL, 2). --define(POSIX_FADV_WILLNEED, 3). --define(POSIX_FADV_DONTNEED, 4). --define(POSIX_FADV_NOREUSE, 5). - -%% Sendfile flags --define(EFILE_SENDFILE_USE_THREADS, 1). +-export([copy/3]). +-include("file_int.hrl"). + +-type prim_file_ref() :: term(). %%% BIFs -export([internal_name2native/1, internal_native2name/1, internal_normalize_utf8/1, - is_translatable/1]). + is_translatable/1]). -type prim_file_name() :: string() | unicode:unicode_binary(). -type prim_file_name_error() :: 'error' | 'ignore' | 'warning'. @@ -185,1316 +83,761 @@ internal_normalize_utf8(_) -> is_translatable(_) -> erlang:nif_error(undefined). -%%% End of BIFs - -%%%----------------------------------------------------------------- -%%% Functions operating on a file through a handle. ?FD_DRV. -%%% -%%% Generic file contents operations. -%%% -%%% Supposed to be called by applications through module file. - - -%% Opens a file using the driver port Port. Returns {error, Reason} -%% | {ok, FileDescriptor} -open(Port, File, ModeList) when is_port(Port), - (is_list(File) orelse is_binary(File)), - is_list(ModeList) -> - case open_mode(ModeList) of - {Mode, _Portopts, _Setopts} -> - open_int(Port, File, Mode, []); - Reason -> - {error, Reason} - end; -open(_,_,_) -> - {error, badarg}. - -%% Opens a file. Returns {error, Reason} | {ok, FileDescriptor}. -open(File, ModeList) when (is_list(File) orelse is_binary(File)), - is_list(ModeList) -> - case open_mode(ModeList) of - {Mode, Portopts, Setopts} -> - open_int({?FD_DRV, Portopts},File, Mode, Setopts); - Reason -> - {error, Reason} - end; -open(_, _) -> - {error, badarg}. +%% This is a janitor process used to close files whose controlling process has +%% died. The emulator will be torn down if this is killed. +delayed_close_loop() -> + receive + {close, FRef} when is_reference(FRef) -> delayed_close_nif(FRef); + _ -> ok + end, + delayed_close_loop(). -%% Opens a port that can be used for open/3 or read_file/2. -%% Returns {ok, Port} | {error, Reason}. -open(Portopts) when is_list(Portopts) -> - drv_open(?FD_DRV, [binary|Portopts]); -open(_) -> - {error, badarg}. +%% -open_int({Driver, Portopts}, File, Mode, Setopts) -> - case drv_open(Driver, Portopts) of - {ok, Port} -> - open_int(Port, File, Mode, Setopts); - {error, _} = Error -> - Error - end; -open_int(Port, File, Mode, Setopts) -> - M = Mode band ?EFILE_MODE_MASK, - case drv_command(Port, [<<?FILE_OPEN, M:32>>, pathname(File)]) of - {ok, Number} -> - open_int_setopts(Port, Number, Setopts); - Error -> - drv_close(Port), - Error - end. +%% Returns {error, Reason} | {ok, BytesCopied} +copy(#file_descriptor{module = ?MODULE} = Source, + #file_descriptor{module = ?MODULE} = Dest, + Length) + when is_integer(Length), Length >= 0; + is_atom(Length) -> + %% XXX Should be moved down to the driver for optimization. + file:copy_opened(Source, Dest, Length). -open_int_setopts(Port, Number, []) -> - {ok, #file_descriptor{module = ?MODULE, data = {Port, Number}}}; -open_int_setopts(Port, Number, [Cmd | Tail]) -> - case drv_command(Port, Cmd) of - ok -> - open_int_setopts(Port, Number, Tail); - Error -> - drv_close(Port), - Error +on_load() -> + Pid = spawn(fun() -> + process_flag(trap_exit, true), + delayed_close_loop() + end), + true = register(erts_prim_file, Pid), + ok = erlang:load_nif(atom_to_list(?MODULE), Pid). + +open(Name, Modes) -> + %% The try/catch pattern seen here is used throughout the file to adhere to + %% the public file interface, which has leaked through for ages because of + %% "raw files." + try open_nif(encode_path(Name), Modes) of + {ok, Ref} -> {ok, make_fd(Ref, Modes)}; + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} end. +make_fd(FRef, Modes) -> + #file_descriptor{module = ?MODULE, data = build_fd_data(FRef, Modes) }. - -%% Returns ok. - -close(#file_descriptor{module = ?MODULE, data = {Port, _}}) -> - case drv_command(Port, <<?FILE_CLOSE>>) of - ok -> - drv_close(Port); - Error -> - Error - end; -%% Closes a port opened with open/1. -close(Port) when is_port(Port) -> - drv_close(Port). - --define(ADVISE(Offs, Len, Adv), - <<?FILE_ADVISE, Offs:64/signed, Len:64/signed, - Adv:32/signed>>). - -%% Returns {error, Reason} | ok. -advise(#file_descriptor{module = ?MODULE, data = {Port, _}}, - Offset, Length, Advise) -> - case Advise of - normal -> - Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_NORMAL), - drv_command(Port, Cmd); - random -> - Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_RANDOM), - drv_command(Port, Cmd); - sequential -> - Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_SEQUENTIAL), - drv_command(Port, Cmd); - will_need -> - Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_WILLNEED), - drv_command(Port, Cmd); - dont_need -> - Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_DONTNEED), - drv_command(Port, Cmd); - no_reuse -> - Cmd = ?ADVISE(Offset, Length, ?POSIX_FADV_NOREUSE), - drv_command(Port, Cmd); - _ -> - {error, einval} +close(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + close_nif(FRef) + catch + error:badarg -> {error, badarg} end. -%% Returns {error, Reason} | ok. -allocate(#file_descriptor{module = ?MODULE, data = {Port, _}}, Offset, Length) -> - Cmd = <<?FILE_ALLOCATE, Offset:64/signed, Length:64/signed>>, - drv_command(Port, Cmd). - -%% Returns {error, Reason} | ok. -write(#file_descriptor{module = ?MODULE, data = {Port, _}}, Bytes) -> - case drv_command_nt(Port, [?FILE_WRITE,erlang:dt_prepend_vm_tag_data(Bytes)],undefined) of - {ok, _Size} -> - ok; - Error -> - Error +read(Fd, Size) -> + try + #{ handle := FRef, + r_ahead_size := RASz, + r_buffer := RBuf } = get_fd_data(Fd), + read_1(FRef, RBuf, prim_buffer:size(RBuf), RASz, Size) + catch + error:badarg -> {error, badarg} end. -%% Returns ok | {error, {WrittenCount, Reason}} -pwrite(#file_descriptor{module = ?MODULE, data = {Port, _}}, L) - when is_list(L) -> - pwrite_int(Port, L, 0, [], []). - -pwrite_int(_, [], 0, [], []) -> - ok; -pwrite_int(Port, [], N, Spec, Data) -> - Header = list_to_binary([?FILE_PWRITEV, erlang:dt_prepend_vm_tag_data(<<N:32>>) | reverse(Spec)]), - case drv_command_nt(Port, [Header | reverse(Data)], undefined) of - {ok, _Size} -> - ok; - Error -> - Error - end; -pwrite_int(Port, [{Offs, Bytes} | T], N, Spec, Data) - when is_integer(Offs) -> - if - -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE -> - pwrite_int(Port, T, N, Spec, Data, Offs, Bytes); - true -> - {error, einval} +-spec read_1(FRef, RBuf, RBufSz, RASz, RSz) -> Result when + FRef :: prim_file_ref(), + RBuf :: term(), + RBufSz :: non_neg_integer(), + RASz :: non_neg_integer(), + RSz :: non_neg_integer(), + Result :: eof | {ok, binary()} | {error, Reason :: atom()}. +read_1(_FRef, RBuf, RBufSz, _RASz, RSz) when RBufSz >= RSz -> + {ok, prim_buffer:read(RBuf, RSz)}; +read_1(FRef, RBuf, RBufSz, RASz, RSz) when RBufSz > 0 -> + Buffered = prim_buffer:read(RBuf, RBufSz), + case read_1(FRef, RBuf, 0, RASz, RSz - RBufSz) of + {ok, Data} -> + {ok, <<Buffered/binary, Data/binary>>}; + eof -> + {ok, Buffered}; + {error, Reason} -> + {error, Reason} end; -pwrite_int(_, [_|_], _N, _Spec, _Data) -> - {error, badarg}. +read_1(FRef, RBuf, RBufSz, RASz, RSz) when RBufSz =:= 0 -> + case read_nif(FRef, RASz + RSz) of + {ok, Data} when byte_size(Data) > RSz -> + {First, Rest} = split_binary(Data, RSz), + prim_buffer:write(RBuf, [Rest]), + {ok, First}; + {ok, Data} when byte_size(Data) =< RSz -> + {ok, Data}; + eof -> + eof; + {error, Reason} -> + {error, Reason} + end. -pwrite_int(Port, T, N, Spec, Data, Offs, Bin) - when is_binary(Bin) -> - Size = byte_size(Bin), - pwrite_int(Port, T, N+1, - [<<Offs:64/signed, Size:64>> | Spec], - [Bin | Data]); -pwrite_int(Port, T, N, Spec, Data, Offs, Bytes) -> - try list_to_binary(Bytes) of - Bin -> - pwrite_int(Port, T, N, Spec, Data, Offs, Bin) +read_line(Fd) -> + try + #{ handle := FRef, + r_ahead_size := RASz, + r_buffer := RBuf } = get_fd_data(Fd), + SearchResult = prim_buffer:find_byte_index(RBuf, $\n), + LineSize = max(?MIN_READLINE_SIZE, RASz), + read_line_1(FRef, RBuf, SearchResult, LineSize) catch - error:Reason -> - {error, Reason} + error:badarg -> {error, badarg} end. - - -%% Returns {error, Reason} | ok. -pwrite(#file_descriptor{module = ?MODULE, data = {Port, _}}, Offs, Bytes) - when is_integer(Offs) -> - if - -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE -> - case pwrite_int(Port, [], 0, [], [], Offs, Bytes) of - {error, {_, Reason}} -> - {error, Reason}; - Result -> - Result - end; - true -> - {error, einval} +-spec read_line_1(FRef, RBuf, SearchResult, LineSize) -> Result when + FRef :: prim_file_ref(), + RBuf :: term(), + SearchResult :: not_found | {ok, non_neg_integer()}, + LineSize :: non_neg_integer(), + Result :: eof | {ok, binary()} | {error, Reason :: atom()}. +read_line_1(FRef, RBuf, not_found, LineSize) -> + case read_nif(FRef, LineSize) of + {ok, Data} -> + prim_buffer:write(RBuf, [Data]), + SearchResult = prim_buffer:find_byte_index(RBuf, $\n), + read_line_1(FRef, RBuf, SearchResult, LineSize); + eof -> + case prim_buffer:size(RBuf) of + Size when Size > 0 -> {ok, prim_buffer:read(RBuf, Size)}; + Size when Size =:= 0 -> eof + end; + {error, Reason} -> + {error, Reason} end; -pwrite(#file_descriptor{module = ?MODULE}, _, _) -> - {error, badarg}. - +read_line_1(_FRef, RBuf, {ok, LFIndex}, _LineSize) -> + %% Translate CRLF into just LF, completely ignoring which encoding is used. + CRIndex = (LFIndex - 1), + case prim_buffer:read(RBuf, LFIndex + 1) of + <<Line:CRIndex/binary, "\r\n">> -> {ok, <<Line/binary, "\n">>}; + Line -> {ok, Line} + end. -%% Returns {error, Reason} | ok. -datasync(#file_descriptor{module = ?MODULE, data = {Port, _}}) -> - drv_command(Port, [?FILE_FDATASYNC]). - -%% Returns {error, Reason} | ok. -sync(#file_descriptor{module = ?MODULE, data = {Port, _}}) -> - drv_command(Port, [?FILE_FSYNC]). - -%% Returns {ok, Data} | eof | {error, Reason}. -read_line(#file_descriptor{module = ?MODULE, data = {Port, _}}) -> - case drv_command(Port, <<?FILE_READ_LINE>>) of - {ok, {0, _Data}} -> - eof; - {ok, {_Size, Data}} -> - {ok, Data}; - {error, enomem} -> - erlang:garbage_collect(), - case drv_command(Port, <<?FILE_READ_LINE>>) of - {ok, {0, _Data}} -> - eof; - {ok, {_Size, Data}} -> - {ok, Data}; - Other -> - Other - end; - Error -> - Error +write(Fd, IOData) -> + try + #{ handle := FRef } = get_fd_data(Fd), + reset_write_position(Fd), + write_1(FRef, erlang:iolist_to_iovec(IOData)) + catch + error:badarg -> {error, badarg} end. - -%% Returns {ok, Data} | eof | {error, Reason}. -read(#file_descriptor{module = ?MODULE, data = {Port, _}}, Size) - when is_integer(Size), 0 =< Size -> - if - Size < ?LARGEFILESIZE -> - case drv_command(Port, <<?FILE_READ, Size:64>>) of - {ok, {0, _Data}} when Size =/= 0 -> - eof; - {ok, {_Size, Data}} -> - {ok, Data}; - {error, enomem} -> - %% Garbage collecting here might help if - %% the current processes have some old binaries left. - erlang:garbage_collect(), - case drv_command(Port, <<?FILE_READ, Size:64>>) of - {ok, {0, _Data}} when Size =/= 0 -> - eof; - {ok, {_Size, Data}} -> - {ok, Data}; - Other -> - Other - end; - Error -> - Error - end; - true -> - {error, einval} +write_1(FRef, IOVec) -> + case write_nif(FRef, IOVec) of + {continue, Remainder} -> + write_1(FRef, Remainder); + ok -> + ok; + {error, Reason} -> + {error, Reason} end. -%% Returns {ok, [Data|eof, ...]} | {error, Reason} -pread(#file_descriptor{module = ?MODULE, data = {Port, _}}, L) - when is_list(L) -> - pread_int(Port, L, 0, []). - -pread_int(_, [], 0, []) -> - {ok, []}; -pread_int(Port, [], N, Spec) -> - drv_command_nt(Port, [?FILE_PREADV, erlang:dt_prepend_vm_tag_data(<<0:32, N:32>>) | reverse(Spec)],undefined); -pread_int(Port, [{Offs, Size} | T], N, Spec) - when is_integer(Offs), is_integer(Size), 0 =< Size -> - if - -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE, - Size < ?LARGEFILESIZE -> - pread_int(Port, T, N+1, [<<Offs:64/signed, Size:64>> | Spec]); - true -> - {error, einval} - end; -pread_int(_, [_|_], _N, _Spec) -> - {error, badarg}. - - - -%% Returns {ok, Data} | eof | {error, Reason}. -pread(#file_descriptor{module = ?MODULE, data = {Port, _}}, Offs, Size) - when is_integer(Offs), is_integer(Size), 0 =< Size -> - if - -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE, - Size < ?LARGEFILESIZE -> - case drv_command_nt(Port, - [?FILE_PREADV, erlang:dt_prepend_vm_tag_data(<<0:32, 1:32, - Offs:64/signed, Size:64>>)], undefined) of - {ok, [eof]} -> - eof; - {ok, [Data]} -> - {ok, Data}; - Error -> - Error - end; - true -> - {error, einval} - end; -pread(#file_descriptor{module = ?MODULE, data = {_, _}}, _, _) -> - {error, badarg}. - - - -%% Returns {ok, Position} | {error, Reason}. -position(#file_descriptor{module = ?MODULE, data = {Port, _}}, At) -> - case lseek_position(At) of - {Offs, Whence} - when -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE -> - drv_command(Port, <<?FILE_LSEEK, Offs:64/signed, Whence:32>>); - {_, _} -> - {error, einval}; - Reason -> - {error, Reason} +truncate(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + reset_write_position(Fd), + truncate_nif(FRef) + catch + error:badarg -> {error, badarg} end. -%% Returns {error, Reason} | ok. -truncate(#file_descriptor{module = ?MODULE, data = {Port, _}}) -> - drv_command(Port, <<?FILE_TRUNCATE>>). - - - -%% Returns {error, Reason} | {ok, BytesCopied} -copy(#file_descriptor{module = ?MODULE} = Source, - #file_descriptor{module = ?MODULE} = Dest, - Length) - when is_integer(Length), Length >= 0; - is_atom(Length) -> - %% XXX Should be moved down to the driver for optimization. - file:copy_opened(Source, Dest, Length). - - - -ipread_s32bu_p32bu(#file_descriptor{module = ?MODULE, - data = {_, _}} = Handle, - Offs, - Infinity) when is_atom(Infinity) -> - ipread_s32bu_p32bu(Handle, Offs, (1 bsl 31)-1); -ipread_s32bu_p32bu(#file_descriptor{module = ?MODULE, data = {Port, _}}, - Offs, - MaxSize) - when is_integer(Offs), is_integer(MaxSize) -> - if - -(?LARGEFILESIZE) =< Offs, Offs < ?LARGEFILESIZE, - 0 =< MaxSize, MaxSize < (1 bsl 31) -> - drv_command(Port, <<?FILE_IPREAD, ?IPREAD_S32BU_P32BU, - Offs:64, MaxSize:32>>); - true -> - {error, einval} - end; -ipread_s32bu_p32bu(#file_descriptor{module = ?MODULE, data = {_, _}}, - _Offs, - _MaxSize) -> - {error, badarg}. +advise(Fd, Offset, Length, Advise) -> + try + #{ handle := FRef } = get_fd_data(Fd), + advise_nif(FRef, Offset, Length, Advise) + catch + error:badarg -> {error, badarg} + end. +allocate(Fd, Offset, Length) -> + try + #{ handle := FRef } = get_fd_data(Fd), + allocate_nif(FRef, Offset, Length) + catch + error:badarg -> {error, badarg} + end. +sync(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + sync_nif(FRef, 0) + catch + error:badarg -> {error, badarg} + end. -%% Returns {ok, Contents} | {error, Reason} -read_file(File) when (is_list(File) orelse is_binary(File)) -> - case drv_open(?FD_DRV, [binary]) of - {ok, Port} -> - Result = read_file(Port, File), - close(Port), - Result; - {error, _} = Error -> - Error - end; -read_file(_) -> - {error, badarg}. +datasync(Fd) -> + try + #{ handle := FRef } = get_fd_data(Fd), + sync_nif(FRef, 1) + catch + error:badarg -> {error, badarg} + end. -%% Takes a Port opened with open/1. -read_file(Port, File) when is_port(Port), - (is_list(File) orelse is_binary(File)) -> - Cmd = [?FILE_READ_FILE | pathname(File)], - case drv_command(Port, Cmd) of - {error, enomem} -> - %% It could possibly help to do a - %% garbage collection here, - %% if the file server has some references - %% to binaries read earlier. - erlang:garbage_collect(), - drv_command(Port, Cmd); - Result -> - Result +position(Fd, {cur, Offset}) -> + try + %% Adjust our current position according to how much we've read ahead. + #{ r_buffer := RBuf } = get_fd_data(Fd), + position_1(Fd, cur, Offset - prim_buffer:size(RBuf)) + catch + error:badarg -> {error, badarg} end; -read_file(_,_) -> - {error, badarg}. - - - -%% Returns {error, Reason} | ok. -write_file(File, Bin) when (is_list(File) orelse is_binary(File)) -> - case open(File, [binary, write]) of - {ok, Handle} -> - Result = write(Handle, Bin), - close(Handle), - Result; - Error -> - Error +position(Fd, {Mark, Offset}) -> + try + position_1(Fd, Mark, Offset) + catch + error:badarg -> {error, badarg} end; -write_file(_, _) -> - {error, badarg}. +position(Fd, cur) -> position(Fd, {cur, 0}); +position(Fd, bof) -> position(Fd, {bof, 0}); +position(Fd, eof) -> position(Fd, {eof, 0}); +position(Fd, Offset) -> position(Fd, {bof, Offset}). +position_1(Fd, Mark, Offset) -> + #{ handle := FRef, r_buffer := RBuf } = get_fd_data(Fd), + prim_buffer:wipe(RBuf), + seek_nif(FRef, Mark, Offset). -%% Returns {error, Reason} | {ok, BytesCopied} -%sendfile(_,_,_,_,_,_,_,_,_,_) -> -% {error, enotsup}; -sendfile(#file_descriptor{module = ?MODULE, data = {Port, _}}, - Dest, Offset, Bytes, _ChunkSize, Headers, Trailers, - Flags) -> - case erlang:port_get_data(Dest) of - Data when Data == inet_tcp; Data == inet6_tcp -> - ok = inet:lock_socket(Dest,true), - {ok, DestFD} = prim_inet:getfd(Dest), - IntFlags = translate_sendfile_flags(Flags), - try drv_command(Port, [<<?FILE_SENDFILE, DestFD:32, - IntFlags:8, - Offset:64/unsigned, - Bytes:64/unsigned, - (iolist_size(Headers)):32/unsigned, - (iolist_size(Trailers)):32/unsigned>>, - Headers,Trailers]) - after - ok = inet:lock_socket(Dest,false) - end; - _Else -> - {error,badarg} +pread(Fd, Offset, Size) -> + try + #{ handle := FRef } = get_fd_data(Fd), + pread_nif(FRef, Offset, Size) + catch + error:badarg -> {error, badarg} end. -translate_sendfile_flags([{use_threads,true}|T]) -> - ?EFILE_SENDFILE_USE_THREADS bor translate_sendfile_flags(T); -translate_sendfile_flags([_|T]) -> - translate_sendfile_flags(T); -translate_sendfile_flags([]) -> - 0. - - -%%%----------------------------------------------------------------- -%%% Functions operating on files without handle to the file. ?DRV. -%%% -%%% Supposed to be called by applications through module file. - - - -%% Returns {ok, Port}, the Port should be used as first argument in all -%% the following functions. Returns {error, Reason} upon failure. -start() -> - drv_open(?DRV, [binary]). - -stop(Port) when is_port(Port) -> - try erlang:port_close(Port) of - _ -> - ok +pread(Fd, LocNums) -> + try + #{ handle := FRef } = get_fd_data(Fd), + pread_list(FRef, LocNums, []) catch - _:_ -> - ok + error:badarg -> {error, badarg} end. +-spec pread_list(FRef, LocNums, ResultList) -> Result when + FRef :: prim_file_ref(), + LocNums :: list({Offset :: non_neg_integer(), + Size :: non_neg_integer()}), + ResultList :: list(eof | binary()), + Result :: {ok, ResultList} | {error, Reason :: atom()}. +pread_list(_FRef, [], ResultList) -> + {ok, reverse_list(ResultList)}; +pread_list(FRef, [{Offset, Size} | Rest], ResultList) -> + case pread_nif(FRef, Offset, Size) of + {ok, Data} -> + pread_list(FRef, Rest, [Data | ResultList]); + eof -> + pread_list(FRef, Rest, [eof | ResultList]); + {error, Reason} -> + {error, Reason} + end. +pwrite(Fd, Offset, IOData) -> + try + #{ handle := FRef, r_buffer := RBuf } = get_fd_data(Fd), + prim_buffer:wipe(RBuf), + pwrite_plain(FRef, Offset, erlang:iolist_to_iovec(IOData)) + catch + error:badarg -> {error, badarg} + end. +pwrite_plain(FRef, Offset, IOVec) -> + case pwrite_nif(FRef, Offset, IOVec) of + {continue, BytesWritten, Remainder} -> + pwrite_plain(FRef, Offset + BytesWritten, Remainder); + ok -> + ok; + {error, Reason} -> + {error, Reason} + end. -%%% The following functions take an optional Port as first argument. -%%% If the port is not supplied, a temporary one is opened and then -%%% closed after the request has been performed. - - - -%% get_cwd/{0,1,2} - -get_cwd() -> - get_cwd_int(0). - -get_cwd(Port) when is_port(Port) -> - get_cwd_int(Port, 0); -get_cwd([]) -> - get_cwd_int(0); -get_cwd([Letter, $: | _]) when $a =< Letter, Letter =< $z -> - get_cwd_int(Letter - $a + 1); -get_cwd([Letter, $: | _]) when $A =< Letter, Letter =< $Z -> - get_cwd_int(Letter - $A + 1); -get_cwd([_|_]) -> - {error, einval}; -get_cwd(_) -> - {error, badarg}. - -get_cwd(Port, []) when is_port(Port) -> - get_cwd_int(Port, 0); -get_cwd(Port, [Letter, $: | _]) - when is_port(Port), $a =< Letter, Letter =< $z -> - get_cwd_int(Port, Letter - $a + 1); -get_cwd(Port, [Letter, $: | _]) - when is_port(Port), $A =< Letter, Letter =< $Z -> - get_cwd_int(Port, Letter - $A + 1); -get_cwd(Port, [_|_]) when is_port(Port) -> - {error, einval}; -get_cwd(_, _) -> - {error, badarg}. - -get_cwd_int(Drive) -> - get_cwd_int({?DRV, [binary]}, Drive). - -get_cwd_int(Port, Drive) -> - drv_command(Port, <<?FILE_PWD, Drive>>, - fun handle_fname_response/1). - - - -%% set_cwd/{1,2} +pwrite(Fd, LocBytes) -> + try + #{ handle := FRef, r_buffer := RBuf } = get_fd_data(Fd), + prim_buffer:wipe(RBuf), + pwrite_list(FRef, LocBytes, 0) + catch + error:badarg -> {error, badarg} + end. -set_cwd(Dir) -> - set_cwd_int({?DRV, [binary]}, Dir). +-spec pwrite_list(FRef, LocBytes, Successes) -> Result when + FRef :: prim_file_ref(), + LocBytes :: list({Offset :: non_neg_integer(), + IOData :: iodata()}), + Successes :: non_neg_integer(), + Result :: ok | {error, {Successes, Reason :: atom()}}. +pwrite_list(_FRef, [], _Successes) -> + ok; +pwrite_list(FRef, [{Offset, IOData} | Rest], Successes) -> + case pwrite_plain(FRef, Offset, erlang:iolist_to_iovec(IOData)) of + {error, Reason} -> {error, {Successes, Reason}}; + ok -> pwrite_list(FRef, Rest, Successes + 1) + end. -set_cwd(Port, Dir) when is_port(Port) -> - set_cwd_int(Port, Dir). +sendfile(Fd, Socket, Offset, Bytes, _ChunkSize, [], [], _Flags) -> + %% There's a very nasty race in here; if we die just prior to duplicating + %% the handle down in the sendfile call, it might get reused by something + %% entirely different and we'll leak unknown data to the socket until it + %% dies soon after. + %% + %% This bug was inherited from the old driver, except it was vulnerable to + %% the bug at any point and not just during setup. + %% + %% We'll have to live with this until we have a way to unambiguously + %% transfer things between drivers or NIFs. Current ideas all fall afoul + %% of the Two Generals problem. + try + advise(Fd, Offset, Bytes, sequential), + prim_inet:sendfile(Socket, get_handle(Fd), Offset, Bytes) + catch + error:badarg -> {error, badarg} + end; +sendfile(_Fd, _Socket, _Offset, _Bytes, _ChunkSize, _Headers, _Trailers, _Flags) -> + {error, enotsup}. -set_cwd_int(Port, Dir) when is_binary(Dir) -> - case prim_file:is_translatable(Dir) of - false -> - {error, no_translation}; - true -> - drv_command(Port, [?FILE_CHDIR, pathname(Dir)]) +%% Undocumented internal function that reads a data block with indirection. +%% +%% This is only used once in DETS and can easily be emulated with pread/2, but +%% it's pretty performance-sensitive so we've implemented it down in the NIF to +%% avoid excessive rescheduling. +-spec ipread_s32bu_p32bu(Fd, Offset, MaxSize) -> Result when + Fd :: #file_descriptor{}, + Offset :: non_neg_integer(), + MaxSize :: non_neg_integer() | infinity, + Result :: {ok, Size :: non_neg_integer(), + Pointer :: non_neg_integer(), + Data :: iodata() | eof} | + eof | + {error, Reason :: atom()}. +ipread_s32bu_p32bu(Fd, Offset, Infinity) when is_atom(Infinity) -> + ipread_s32bu_p32bu(Fd, Offset, (1 bsl 31) - 1); +ipread_s32bu_p32bu(Fd, Offset, MaxSize) + when is_integer(Offset), is_integer(MaxSize) -> + try + #{ handle := FRef } = get_fd_data(Fd), + ipread_s32bu_p32bu_nif(FRef, Offset, MaxSize) + catch + error:badarg -> {error, badarg} end; -set_cwd_int(Port, Dir) when is_list(Dir) -> - drv_command(Port, [?FILE_CHDIR, pathname(Dir)]); -set_cwd_int(_, _) -> +ipread_s32bu_p32bu(_Fd, _Offset, _MaxSize) -> {error, badarg}. - - - -%% delete/{1,2} - -delete(File) -> - delete_int({?DRV, [binary]}, File). - -delete(Port, File) when is_port(Port) -> - delete_int(Port, File). - -delete_int(Port, File) -> - drv_command(Port, [?FILE_DELETE, pathname(File)]). - - - -%% rename/{2,3} - -rename(From, To) -> - rename_int({?DRV, [binary]}, From, To). - -rename(Port, From, To) when is_port(Port) -> - rename_int(Port, From, To). - -rename_int(Port, From, To) -> - drv_command(Port, [?FILE_RENAME, pathname(From), pathname(To)]). - - - -%% make_dir/{1,2} - -make_dir(Dir) -> - make_dir_int({?DRV, [binary]}, Dir). - -make_dir(Port, Dir) when is_port(Port) -> - make_dir_int(Port, Dir). - -make_dir_int(Port, Dir) -> - drv_command(Port, [?FILE_MKDIR, pathname(Dir)]). - - - -%% del_dir/{1,2} - -del_dir(Dir) -> - del_dir_int({?DRV, [binary]}, Dir). - -del_dir(Port, Dir) when is_port(Port) -> - del_dir_int(Port, Dir). - -del_dir_int(Port, Dir) -> - drv_command(Port, [?FILE_RMDIR, pathname(Dir)]). - - - -%% read_file_info/{1,2,3} - -read_file_info(File) -> - read_file_info_int({?DRV, [binary]}, File, local). - -read_file_info(Port, File) when is_port(Port) -> - read_file_info_int(Port, File, local); -read_file_info(File, Opts) -> - read_file_info_int({?DRV, [binary]}, File, plgv(time, Opts, local)). - -read_file_info(Port, File, Opts) when is_port(Port) -> - read_file_info_int(Port, File, plgv(time, Opts, local)). - -read_file_info_int(Port, File, TimeType) -> +ipread_s32bu_p32bu_nif(_FRef, _Offset, _MaxSize) -> + erlang:nif_error(undef). + +%% Returns the binary representation of the underlying handle, for use in +%% tricky operations like sendfile/8. +-spec get_handle(Fd) -> Result when + Fd :: #file_descriptor{}, + Result :: binary() | {error, Reason :: atom()}. +get_handle(Fd) -> try - case drv_command(Port, [?FILE_FSTAT, pathname(File)]) of - {ok, FI} -> {ok, FI#file_info{ - ctime = from_seconds(FI#file_info.ctime, TimeType), - mtime = from_seconds(FI#file_info.mtime, TimeType), - atime = from_seconds(FI#file_info.atime, TimeType) - }}; - Error -> Error - end + #{ handle := FRef } = get_fd_data(Fd), + get_handle_nif(FRef) catch - error:_ -> {error, badarg} + error:badarg -> {error, badarg} end. +%% Resets the write head to the position the user believes we're at, which may +%% not be the same as the real one when read caching is in effect. +reset_write_position(Fd) -> + #{ r_buffer := RBuf } = Fd#file_descriptor.data, + case prim_buffer:size(RBuf) of + Size when Size > 0 -> position(Fd, cur); + Size when Size =:= 0 -> ok + end. -%% altname/{1,2} - -altname(File) -> - altname_int({?DRV, [binary]}, File). - -altname(Port, File) when is_port(Port) -> - altname_int(Port, File). - -altname_int(Port, File) -> - drv_command(Port, [?FILE_ALTNAME, pathname(File)], - fun handle_fname_response/1). - -%% write_file_info/{2,3,4} - -write_file_info(File, Info) -> - write_file_info_int({?DRV, [binary]}, File, Info, local). - -write_file_info(Port, File, Info) when is_port(Port) -> - write_file_info_int(Port, File, Info, local); -write_file_info(File, Info, Opts) -> - write_file_info_int({?DRV, [binary]}, File, Info, plgv(time, Opts, local)). - -write_file_info(Port, File, Info, Opts) when is_port(Port) -> - write_file_info_int(Port, File, Info, plgv(time, Opts, local)). +get_fd_data(#file_descriptor{ data = Data }) -> + #{ owner := Owner } = Data, + case self() of + Owner -> Data; + _ -> error(not_on_controlling_process) + end. -write_file_info_int(Port, File, - #file_info{mode=Mode, - uid=Uid, - gid=Gid, - atime=Atime0, - mtime=Mtime0, - ctime=Ctime0}, - TimeType) -> +build_fd_data(FRef, Modes) -> + Defaults = + #{ owner => self(), + handle => FRef, + r_ahead_size => 0, + r_buffer => prim_buffer:new() }, + fill_fd_option_map(Modes, Defaults). + +fill_fd_option_map([], Map) -> + Map; + +fill_fd_option_map([read_ahead | Modes], Map) -> + fill_fd_option_map([{read_ahead, 64 bsl 10} | Modes], Map); +fill_fd_option_map([{read_ahead, Size} | Modes], Map) -> + fill_fd_option_map(Modes, Map#{ r_ahead_size => Size }); + +fill_fd_option_map([_Ignored | Modes], Map) -> + fill_fd_option_map(Modes, Map). + +open_nif(_Name, _Modes) -> + erlang:nif_error(undef). +close_nif(_FileRef) -> + erlang:nif_error(undef). +read_nif(_FileRef, _Size) -> + erlang:nif_error(undef). +write_nif(_FileRef, _IOVec) -> + erlang:nif_error(undef). +pread_nif(_FileRef, _Offset, _Size) -> + erlang:nif_error(undef). +pwrite_nif(_FileRef, _Offset, _IOVec) -> + erlang:nif_error(undef). +seek_nif(_FileRef, _Mark, _Offset) -> + erlang:nif_error(undef). +sync_nif(_FileRef, _DataOnly) -> + erlang:nif_error(undef). +advise_nif(_FileRef, _Offset, _Length, _Advise) -> + erlang:nif_error(undef). +allocate_nif(_FileRef, _Offset, _Length) -> + erlang:nif_error(undef). +truncate_nif(_FileRef) -> + erlang:nif_error(undef). +get_handle_nif(_FileRef) -> + erlang:nif_error(undef). +delayed_close_nif(_FileRef) -> + erlang:nif_error(undef). - % Atime and/or Mtime might be undefined - % - use localtime() for atime, if atime is undefined - % - use atime as mtime if mtime is undefined - % - use mtime as ctime if ctime is undefined +%% +%% Quality-of-life helpers +%% +read_file(Filename) -> + %% We're doing this operation in the NIF to avoid excessive rescheduling. try - Atime = file_info_validate_atime(Atime0, TimeType), - Mtime = file_info_validate_mtime(Mtime0, Atime), - Ctime = file_info_validate_ctime(Ctime0, Mtime), - - drv_command(Port, [?FILE_WRITE_INFO, - int_to_int32bytes(Mode), - int_to_int32bytes(Uid), - int_to_int32bytes(Gid), - int_to_int64bytes(to_seconds(Atime, TimeType)), - int_to_int64bytes(to_seconds(Mtime, TimeType)), - int_to_int64bytes(to_seconds(Ctime, TimeType)), - pathname(File)]) + read_file_nif(encode_path(Filename)) catch - error:_ -> {error, badarg} + error:badarg -> {error, badarg} + end. +read_file_nif(_Filename) -> + erlang:nif_error(undef). + +write_file(Filename, Bytes) -> + write_file(Filename, Bytes, []). +write_file(Filename, Bytes, Modes) -> + case open(Filename, [write, binary | Modes]) of + {ok, Fd} -> + Result = write(Fd, Bytes), + close(Fd), + Result; + {error, Reason} -> + {error, Reason} end. +%% +%% Filesystem operations +%% -file_info_validate_atime(Atime, _) when Atime =/= undefined -> Atime; -file_info_validate_atime(undefined, local) -> erlang:localtime(); -file_info_validate_atime(undefined, universal) -> erlang:universaltime(); -file_info_validate_atime(undefined, posix) -> erlang:universaltime_to_posixtime(erlang:universaltime()). - -file_info_validate_mtime(undefined, Atime) -> Atime; -file_info_validate_mtime(Mtime, _) -> Mtime. - -file_info_validate_ctime(undefined, Mtime) -> Mtime; -file_info_validate_ctime(Ctime, _) -> Ctime. - -%% make_link/{2,3} - -make_link(Old, New) -> - make_link_int({?DRV, [binary]}, Old, New). - -make_link(Port, Old, New) when is_port(Port) -> - make_link_int(Port, Old, New). - -make_link_int(Port, Old, New) -> - drv_command(Port, [?FILE_LINK, pathname(Old), pathname(New)]). - - - -%% make_symlink/{2,3} - -make_symlink(Old, New) -> - make_symlink_int({?DRV, [binary]}, Old, New). - -make_symlink(Port, Old, New) when is_port(Port) -> - make_symlink_int(Port, Old, New). - -make_symlink_int(Port, Old, New) -> - drv_command(Port, [?FILE_SYMLINK, pathname(Old), pathname(New)]). - - - -%% read_link/{2,3} - -read_link(Link) -> - read_link_int({?DRV, [binary]}, Link). - -read_link(Port, Link) when is_port(Port) -> - read_link_int(Port, Link). - -read_link_int(Port, Link) -> - drv_command(Port, [?FILE_READLINK, pathname(Link)], - fun handle_fname_response/1). - -%% read_link_all/{2,3} - -read_link_all(Link) -> - read_link_all_int({?DRV, [binary]}, Link). - -read_link_all(Port, Link) when is_port(Port) -> - read_link_all_int(Port, Link). - -read_link_all_int(Port, Link) -> - drv_command(Port, [?FILE_READLINK, pathname(Link)], - fun handle_fname_response_all/1). - +read_link(Name) -> read_link_1(Name, false). +read_link_all(Name) -> read_link_1(Name, true). +read_link_1(Name, AcceptRawNames) -> + try read_link_nif(encode_path(Name)) of + {ok, RawName} -> translate_raw_name(RawName, AcceptRawNames); + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} + end. -%% read_link_info/{2,3} +translate_raw_name(RawName, SilentFailure) -> + case decode_path(RawName) of + Converted when is_list(Converted) -> {ok, Converted}; + {error, _Reason} when SilentFailure =:= false -> {error, einval}; + {error, _Reason} when SilentFailure =:= true -> {ok, RawName} + end. -read_link_info(Link) -> - read_link_info_int({?DRV, [binary]}, Link, local). +list_dir(Name) -> list_dir_1(Name, true). +list_dir_all(Name) -> list_dir_1(Name, false). -read_link_info(Port, Link) when is_port(Port) -> - read_link_info_int(Port, Link, local); +list_dir_1(Name, SkipInvalid) -> + try list_dir_nif(encode_path(Name)) of + {ok, RawNames} -> list_dir_convert(RawNames, SkipInvalid, []); + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} + end. -read_link_info(Link, Opts) -> - read_link_info_int({?DRV, [binary]}, Link, plgv(time, Opts, local)). +list_dir_convert([], _SkipInvalid, Result) -> + {ok, Result}; +list_dir_convert([RawName | Rest], SkipInvalid, Result) -> + case decode_path(RawName) of + Converted when is_list(Converted) -> + list_dir_convert(Rest, SkipInvalid, [Converted | Result]); + {error, _} when SkipInvalid =:= false -> + list_dir_convert(Rest, SkipInvalid, [RawName | Result]); + + %% If the filename cannot be converted, return error or ignore with + %% optional error logger warning depending on +fn{u|a}{i|e|w} emulator + %% switches. + {error, ignore} -> + list_dir_convert(Rest, SkipInvalid, Result); + {error, warning} -> + %% this is equal to calling error_logger:warning_msg/2 which + %% we don't want to do from code_server during system boot + logger ! {log,warning,"Non-unicode filename ~p ignored\n", [RawName], + #{pid=>self(), + gl=>group_leader(), + time=>erlang:system_time(microsecond), + error_logger=>#{tag=>warning_msg}}}, + list_dir_convert(Rest, SkipInvalid, Result); + {error, _} -> + {error, {no_translation, RawName}} + end. -read_link_info(Port, Link, Opts) when is_port(Port) -> - read_link_info_int(Port, Link, plgv(time, Opts, local)). +read_file_info(Filename) -> + read_info_1(Filename, 1, local). +read_file_info(Filename, Opts) -> + read_info_1(Filename, 1, proplist_get_value(time, Opts, local)). +read_link_info(Name) -> + read_info_1(Name, 0, local). +read_link_info(Name, Opts) -> + read_info_1(Name, 0, proplist_get_value(time, Opts, local)). -read_link_info_int(Port, Link, TimeType) -> +read_info_1(Name, FollowLinks, TimeType) -> try - case drv_command(Port, [?FILE_LSTAT, pathname(Link)]) of - {ok, FI} -> {ok, FI#file_info{ - ctime = from_seconds(FI#file_info.ctime, TimeType), - mtime = from_seconds(FI#file_info.mtime, TimeType), - atime = from_seconds(FI#file_info.atime, TimeType) - }}; - Error -> Error - end + case read_info_nif(encode_path(Name), FollowLinks) of + {error, Reason} -> + {error, Reason}; + FileInfo -> + CTime = from_posix_seconds(FileInfo#file_info.ctime, TimeType), + MTime = from_posix_seconds(FileInfo#file_info.mtime, TimeType), + ATime = from_posix_seconds(FileInfo#file_info.atime, TimeType), + {ok, FileInfo#file_info{ ctime = CTime, + mtime = MTime, + atime = ATime }} + end catch - error:_ -> {error, badarg} + error:_ -> {error, badarg} end. -%% list_dir/{1,2} - -list_dir(Dir) -> - list_dir_int({?DRV, [binary]}, Dir). - -list_dir(Port, Dir) when is_port(Port) -> - list_dir_int(Port, Dir). - -list_dir_int(Port, Dir) -> - drv_command(Port, [?FILE_READDIR, pathname(Dir)], - fun(P) -> - case list_dir_response(P, []) of - {ok, RawNames} -> - try - {ok, list_dir_convert(RawNames)} - catch - throw:Reason -> - Reason - end; - Error -> - Error - end - end). - -list_dir_all(Dir) -> - list_dir_all_int({?DRV, [binary]}, Dir). - -list_dir_all(Port, Dir) when is_port(Port) -> - list_dir_all_int(Port, Dir). - -list_dir_all_int(Port, Dir) -> - drv_command(Port, [?FILE_READDIR, pathname(Dir)], - fun(P) -> - case list_dir_response(P, []) of - {ok, RawNames} -> - {ok, list_dir_convert_all(RawNames)}; - Error -> - Error - end - end). - -list_dir_response(Port, Acc0) -> - case drv_get_response(Port) of - {lfname, []} -> - {ok, Acc0}; - {lfname, Names} -> - Acc = [Name || <<L:16,Name:L/binary>> <= Names] ++ Acc0, - list_dir_response(Port, Acc); - Error -> - Error +write_file_info(Filename, Info) -> + write_file_info_1(Filename, Info, local). +write_file_info(Filename, Info, Opts) -> + write_file_info_1(Filename, Info, proplist_get_value(time, Opts, local)). + +write_file_info_1(Filename, Info, TimeType) -> + #file_info{ mode = Modes, + uid = Uid, + gid = Gid, + atime = ATime0, + mtime = MTime0, + ctime = CTime0} = Info, + try + % ATime and/or MTime might be undefined + % - use localtime() for atime, if atime is undefined + % - use atime as mtime if mtime is undefined + % - use mtime as ctime if ctime is undefined + ATime = file_info_convert_atime(ATime0, TimeType), + MTime = file_info_convert_mtime(MTime0, ATime, TimeType), + CTime = file_info_convert_ctime(CTime0, MTime, TimeType), + EncodedName = encode_path(Filename), + + %% This is a bit ugly but we need to handle partial failures the same + %% way the old driver did. + throw_on_error(set_owner(EncodedName, Uid, Gid)), + throw_on_error(set_permissions(EncodedName, Modes)), + throw_on_error(set_time(EncodedName, ATime, MTime, CTime)) + catch + throw:Reason -> {error, Reason}; + error:_ -> {error, badarg} end. -list_dir_convert([Name|Names]) -> - %% If the filename cannot be converted, return error or ignore - %% with optional error logger warning, depending on +fn{u|a}{i|e|w} - %% emulator switches. - case prim_file:internal_native2name(Name) of - {error, warning} -> - error_logger:warning_msg("Non-unicode filename ~p ignored\n", - [Name]), - list_dir_convert(Names); - {error, ignore} -> - list_dir_convert(Names); - {error, error} -> - throw({error, {no_translation, Name}}); - Converted when is_list(Converted) -> - [Converted|list_dir_convert(Names)] - end; -list_dir_convert([]) -> []. - -list_dir_convert_all([Name|Names]) -> - %% If the filename cannot be converted, retain the filename as - %% a binary. - case prim_file:internal_native2name(Name) of - {error, _} -> - [Name|list_dir_convert_all(Names)]; - Converted when is_list(Converted) -> - [Converted|list_dir_convert_all(Names)] - end; -list_dir_convert_all([]) -> []. - -%%%----------------------------------------------------------------- -%%% Functions to communicate with the driver - -handle_fname_response(Port) -> - case drv_get_response(Port) of - {fname, Name} -> - case prim_file:internal_native2name(Name) of - {error, warning} -> - error_logger:warning_msg("Non-unicode filename ~p " - "ignored when reading link\n", - [Name]), - {error, einval}; - {error, _} -> - {error, einval}; - Converted when is_list(Converted) -> - {ok, Converted} - end; - Error -> - Error - end. +set_owner(EncodedName, Uid, undefined) -> + set_owner(EncodedName, Uid, -1); +set_owner(EncodedName, undefined, Gid) -> + set_owner(EncodedName, -1, Gid); +set_owner(EncodedName, Uid, Gid) -> + set_owner_nif(EncodedName, Uid, Gid). +set_owner_nif(_Path, _Uid, _Gid) -> + erlang:nif_error(undef). -handle_fname_response_all(Port) -> - case drv_get_response(Port) of - {fname, Name} -> - case prim_file:internal_native2name(Name) of - {error, _} -> - {ok, Name}; - Converted when is_list(Converted) -> - {ok, Converted} - end; - Error -> - Error +set_permissions(_EncodedName, undefined) -> + ok; +set_permissions(EncodedName, Permissions) -> + set_permissions_nif(EncodedName, Permissions). +set_permissions_nif(_Path, _Permissions) -> + erlang:nif_error(undef). + +set_time(EncodedName, ATime, MTime, CTime) -> + set_time_nif(EncodedName, ATime, MTime, CTime). +set_time_nif(_Path, _ATime, _MTime, _CTime) -> + erlang:nif_error(undef). + +throw_on_error(ok) -> ok; +throw_on_error({error, enotsup}) -> ok; +throw_on_error({error, Reason}) -> throw(Reason). + +file_info_convert_atime(ATime, TimeType) when ATime =/= undefined -> + to_posix_seconds(ATime, TimeType); +file_info_convert_atime(undefined, local) -> + to_posix_seconds(erlang:localtime(), local); +file_info_convert_atime(undefined, universal) -> + to_posix_seconds(erlang:universaltime(), universal); +file_info_convert_atime(undefined, posix) -> + erlang:universaltime_to_posixtime(erlang:universaltime()). + +file_info_convert_mtime(undefined, ATime, _TimeType) -> + ATime; +file_info_convert_mtime(MTime, _ATime, TimeType) -> + to_posix_seconds(MTime, TimeType). + +file_info_convert_ctime(undefined, MTime, _TimeType) -> + MTime; +file_info_convert_ctime(CTime, _MTime, TimeType) -> + to_posix_seconds(CTime, TimeType). + +%% This is only relevant on Windows, so we assume that format to simplify the +%% internals. +get_cwd([Letter, $:]) when Letter >= $A, Letter =< $Z -> + get_dcwd(Letter - $A + 1); +get_cwd([Letter, $:]) when Letter >= $a, Letter =< $z -> + get_dcwd(Letter - $a + 1); +get_cwd([_|_]) -> + {error, einval}; +get_cwd(_) -> + {error, badarg}. +get_dcwd(Index) -> + try get_device_cwd_nif(Index) of + {ok, RawPath} -> {ok, decode_path(RawPath)}; + {error, Reason} -> {error, Reason} + catch + error:badarg -> {error, badarg} end. -%% Opens a driver port and converts any problems into {error, emfile}. -%% Returns {ok, Port} when successful. - -drv_open(Driver, Portopts) -> - try erlang:open_port({spawn_driver, Driver}, Portopts) of - Port -> - {ok, Port} +get_cwd() -> + try get_cwd_nif() of + {ok, RawPath} -> {ok, decode_path(RawPath)}; + {error, Reason} -> {error, Reason} catch - error:Reason -> - {error, Reason} + error:badarg -> {error, badarg} end. - - - -%% Closes a port in a safe way. Returns ok. - -drv_close(Port) -> - Save = erlang:dt_spread_tag(false), +set_cwd(Path) -> try - try erlang:port_close(Port) catch error:_ -> ok end, - receive %% Ugly workaround in case the caller==owner traps exits - {'EXIT', Port, _Reason} -> - ok - after 0 -> - ok - end - after - erlang:dt_restore_tag(Save) + case is_path_translatable(Path) of + true -> set_cwd_nif(encode_path(Path)); + false -> {error, no_translation} + end + catch + error:badarg -> {error, badarg} end. - - -%% Issues a command to a port and gets the response. -%% If Port is {Driver, Portopts} a port is first opened and -%% then closed after the result has been received. -%% Returns {ok, Result} or {error, Reason}. - -drv_command(Port, Command) -> - drv_command(Port, Command, undefined). - -drv_command(Port, Command, R) when is_binary(Command) -> - drv_command(Port, Command, true, R); -drv_command(Port, Command, R) -> - try erlang:iolist_size(Command) of - _ -> - drv_command(Port, Command, true, R) +delete(Path) -> + try + del_file_nif(encode_path(Path)) catch - error:Reason -> - {error, Reason} + error:badarg -> {error, badarg} end. -drv_command(Port, Command, Validated, R) when is_port(Port) -> - Save = erlang:dt_spread_tag(false), - try erlang:port_command(Port, erlang:dt_append_vm_tag_data(Command)) of - true -> - drv_get_response(Port, R) +rename(Source, Destination) -> + try + rename_nif(encode_path(Source), encode_path(Destination)) catch - %% If the Command is valid, knowing that the port is a port, - %% a badarg error must mean it is a dead port, that is: - %% a currently invalid filehandle, -> einval, not badarg. - error:badarg when Validated -> - {error, einval}; - error:badarg -> - try erlang:iolist_size(Command) of - _ -> % Valid - {error, einval} - catch - error:_ -> - {error, badarg} - end; - error:Reason -> - {error, Reason} - after - erlang:dt_restore_tag(Save) - end; -drv_command({Driver, Portopts}, Command, Validated, R) -> - case drv_open(Driver, Portopts) of - {ok, Port} -> - Result = drv_command(Port, Command, Validated, R), - drv_close(Port), - Result; - Error -> - Error + error:badarg -> {error, badarg} end. -drv_command_nt(Port, Command, R) when is_port(Port) -> - Save = erlang:dt_spread_tag(false), - try erlang:port_command(Port, Command) of - true -> - drv_get_response(Port, R) +make_dir(Path) -> + try + make_dir_nif(encode_path(Path)) catch - error:badarg -> - try erlang:iolist_size(Command) of - _ -> % Valid - {error, einval} - catch - error:_ -> - {error, badarg} - end; - error:Reason -> - {error, Reason} - after - erlang:dt_restore_tag(Save) + error:badarg -> {error, badarg} end. - - - -%% Receives the response from a driver port. -%% Returns: {ok, ListOrBinary}|{error, Reason} - -drv_get_response(Port, undefined) -> - drv_get_response(Port); -drv_get_response(Port, Fun) when is_function(Fun, 1) -> - Fun(Port). - -drv_get_response(Port) -> - erlang:bump_reductions(100), - receive - {Port, {data, [Response|Rest] = Data}} -> - try translate_response(Response, Rest) - catch - error:Reason -> - {error, {bad_response_from_port, Data, - {Reason, erlang:get_stacktrace()}}} - end; - {'EXIT', Port, Reason} -> - {error, {port_died, Reason}} +del_dir(Path) -> + try + del_dir_nif(encode_path(Path)) + catch + error:badarg -> {error, badarg} end. - - -%%%----------------------------------------------------------------- -%%% Utility functions. - -%% Converts a list of mode atoms into a mode word for the driver. -%% Returns {Mode, Portopts, Setopts} where Portopts is a list of -%% options for erlang:open_port/2 and Setopts is a list of -%% setopt commands to send to the port, or error Reason upon failure. - -open_mode(List) when is_list(List) -> - case open_mode(List, 0, [], []) of - {Mode, Portopts, Setopts} when Mode band - (?EFILE_MODE_READ bor ?EFILE_MODE_WRITE) - =:= 0 -> - {Mode bor ?EFILE_MODE_READ, Portopts, Setopts}; - Other -> - Other +make_link(Existing, New) -> + try + make_hard_link_nif(encode_path(Existing), encode_path(New)) + catch + error:badarg -> {error, badarg} end. - -open_mode([raw|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode, Portopts, Setopts); -open_mode([read|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_MODE_READ, Portopts, Setopts); -open_mode([write|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_MODE_WRITE, Portopts, Setopts); -open_mode([binary|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode, [binary | Portopts], Setopts); -open_mode([compressed|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_COMPRESSED, Portopts, Setopts); -open_mode([append|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_MODE_APPEND bor ?EFILE_MODE_WRITE, - Portopts, Setopts); -open_mode([exclusive|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_MODE_EXCL, Portopts, Setopts); -open_mode([sync|Rest], Mode, Portopts, Setopts) -> - open_mode(Rest, Mode bor ?EFILE_MODE_SYNC, Portopts, Setopts); -open_mode([delayed_write|Rest], Mode, Portopts, Setopts) -> - open_mode([{delayed_write, 64*1024, 2000}|Rest], Mode, - Portopts, Setopts); -open_mode([{delayed_write, Size, Delay}|Rest], Mode, Portopts, Setopts) - when is_integer(Size), 0 =< Size, is_integer(Delay), 0 =< Delay -> - if - Size < ?LARGEFILESIZE, Delay < 1 bsl 64 -> - open_mode(Rest, Mode, Portopts, - [<<?FILE_SETOPT, ?FILE_OPT_DELAYED_WRITE, - Size:64, Delay:64>> - | Setopts]); - true -> - einval - end; -open_mode([read_ahead|Rest], Mode, Portopts, Setopts) -> - open_mode([{read_ahead, 64*1024}|Rest], Mode, Portopts, Setopts); -open_mode([{read_ahead, Size}|Rest], Mode, Portopts, Setopts) - when is_integer(Size), 0 =< Size -> - if - Size < ?LARGEFILESIZE -> - open_mode(Rest, Mode, Portopts, - [<<?FILE_SETOPT, ?FILE_OPT_READ_AHEAD, - Size:64>> | Setopts]); - true -> - einval - end; -open_mode([], Mode, Portopts, Setopts) -> - {Mode, reverse(Portopts), reverse(Setopts)}; -open_mode(_, _Mode, _Portopts, _Setopts) -> - badarg. - - - -%% Converts a position tuple {bof, X} | {cur, X} | {eof, X} into -%% {Offset, OriginCode} for the driver. -%% Returns badarg upon failure. - -lseek_position(Pos) - when is_integer(Pos) -> - lseek_position({bof, Pos}); -lseek_position(bof) -> - lseek_position({bof, 0}); -lseek_position(cur) -> - lseek_position({cur, 0}); -lseek_position(eof) -> - lseek_position({eof, 0}); -lseek_position({bof, Offset}) - when is_integer(Offset) -> - {Offset, ?EFILE_SEEK_SET}; -lseek_position({cur, Offset}) - when is_integer(Offset) -> - {Offset, ?EFILE_SEEK_CUR}; -lseek_position({eof, Offset}) - when is_integer(Offset) -> - {Offset, ?EFILE_SEEK_END}; -lseek_position(_) -> - badarg. - - - -%% Translates the response from the driver into -%% {ok, Result} or {error, Reason}. - --dialyzer({no_improper_lists, translate_response/2}). -translate_response(?FILE_RESP_OK, []) -> - ok; -translate_response(?FILE_RESP_ERROR, List) when is_list(List) -> - {error, list_to_atom(List)}; -translate_response(?FILE_RESP_NUMBER, List) -> - {N, []} = get_uint64(List), - {ok, N}; -translate_response(?FILE_RESP_DATA, List) -> - {_N, _Data} = ND = get_uint64(List), - {ok, ND}; -translate_response(?FILE_RESP_INFO, List) when is_list(List) -> - {ok, transform_info(List)}; -translate_response(?FILE_RESP_NUMERR, L0) -> - {N, L1} = get_uint64(L0), - {error, {N, list_to_atom(L1)}}; -translate_response(?FILE_RESP_LDATA, List) -> - {ok, transform_ldata(List)}; -translate_response(?FILE_RESP_N2DATA, - <<Offset:64, 0:64, Size:64>>) -> - {ok, {Size, Offset, eof}}; -translate_response(?FILE_RESP_N2DATA, - [<<Offset:64, 0:64, Size:64>> | <<>>]) -> - {ok, {Size, Offset, eof}}; -translate_response(?FILE_RESP_N2DATA = X, - [<<_:64, 0:64, _:64>> | _] = Data) -> - {error, {bad_response_from_port, [X | Data]}}; -translate_response(?FILE_RESP_N2DATA = X, - [<<_:64, _:64, _:64>> | <<>>] = Data) -> - {error, {bad_response_from_port, [X | Data]}}; -translate_response(?FILE_RESP_N2DATA, - [<<Offset:64, _ReadSize:64, Size:64>> | D]) -> - {ok, {Size, Offset, D}}; -translate_response(?FILE_RESP_N2DATA = X, L0) when is_list(L0) -> - {Offset, L1} = get_uint64(L0), - {ReadSize, L2} = get_uint64(L1), - {Size, L3} = get_uint64(L2), - case {ReadSize, L3} of - {0, []} -> - {ok, {Size, Offset, eof}}; - {0, _} -> - {error, {bad_response_from_port, [X | L0]}}; - {_, []} -> - {error, {bad_response_from_port, [X | L0]}}; - _ -> - {ok, {Size, Offset, L3}} - end; -translate_response(?FILE_RESP_EOF, []) -> - eof; -translate_response(?FILE_RESP_FNAME, Data) -> - {fname, Data}; -translate_response(?FILE_RESP_LFNAME, Data) -> - {lfname, Data}; -translate_response(?FILE_RESP_ALL_DATA, Data) -> - {ok, Data}; -translate_response(X, Data) -> - {error, {bad_response_from_port, [X | Data]}}. - -transform_info([ - Hsize1, Hsize2, Hsize3, Hsize4, - Lsize1, Lsize2, Lsize3, Lsize4, - Type1, Type2, Type3, Type4, - Atime1, Atime2, Atime3, Atime4, Atime5, Atime6, Atime7, Atime8, - Mtime1, Mtime2, Mtime3, Mtime4, Mtime5, Mtime6, Mtime7, Mtime8, - Ctime1, Ctime2, Ctime3, Ctime4, Ctime5, Ctime6, Ctime7, Ctime8, - Mode1, Mode2, Mode3, Mode4, - Links1, Links2, Links3, Links4, - Major1, Major2, Major3, Major4, - Minor1, Minor2, Minor3, Minor4, - Inode1, Inode2, Inode3, Inode4, - Uid1, Uid2, Uid3, Uid4, - Gid1, Gid2, Gid3, Gid4, - Access1,Access2,Access3,Access4]) -> - #file_info { - size = uint32(Hsize1,Hsize2,Hsize3,Hsize4)*16#100000000 + uint32(Lsize1,Lsize2,Lsize3,Lsize4), - type = file_type(uint32(Type1,Type2,Type3,Type4)), - access = file_access(uint32(Access1,Access2,Access3,Access4)), - atime = sint64(Atime1, Atime2, Atime3, Atime4, Atime5, Atime6, Atime7, Atime8), - mtime = sint64(Mtime1, Mtime2, Mtime3, Mtime4, Mtime5, Mtime6, Mtime7, Mtime8), - ctime = sint64(Ctime1, Ctime2, Ctime3, Ctime4, Ctime5, Ctime6, Ctime7, Ctime8), - mode = uint32(Mode1,Mode2,Mode3,Mode4), - links = uint32(Links1,Links2,Links3,Links4), - major_device = uint32(Major1,Major2,Major3,Major4), - minor_device = uint32(Minor1,Minor2,Minor3,Minor4), - inode = uint32(Inode1,Inode2,Inode3,Inode4), - uid = uint32(Uid1,Uid2,Uid3,Uid4), - gid = uint32(Gid1,Gid2,Gid3,Gid4) - }. - - -file_type(1) -> device; -file_type(2) -> directory; -file_type(3) -> regular; -file_type(4) -> symlink; -file_type(_) -> other. - -file_access(0) -> none; -file_access(1) -> write; -file_access(2) -> read; -file_access(3) -> read_write. - -int_to_int32bytes(Int) when is_integer(Int) -> - <<Int:32>>; -int_to_int32bytes(undefined) -> - <<-1:32>>. - -int_to_int64bytes(Int) when is_integer(Int) -> - <<Int:64/signed>>. - - -sint64(I1,I2,I3,I4,I5,I6,I7,I8) when I1 > 127 -> - ((I1 bsl 56) bor (I2 bsl 48) bor (I3 bsl 40) bor (I4 bsl 32) bor - (I5 bsl 24) bor (I6 bsl 16) bor (I7 bsl 8) bor I8) - (1 bsl 64); -sint64(I1,I2,I3,I4,I5,I6,I7,I8) -> - ((I1 bsl 56) bor (I2 bsl 48) bor (I3 bsl 40) bor (I4 bsl 32) bor - (I5 bsl 24) bor (I6 bsl 16) bor (I7 bsl 8) bor I8). - - -uint32(X1,X2,X3,X4) -> - (X1 bsl 24) bor (X2 bsl 16) bor (X3 bsl 8) bor X4. - -get_uint64(L0) -> - {X1, L1} = get_uint32(L0), - {X2, L2} = get_uint32(L1), - {(X1 bsl 32) bor X2, L2}. - -get_uint32([X1,X2,X3,X4|List]) -> - {(((((X1 bsl 8) bor X2) bsl 8) bor X3) bsl 8) bor X4, List}. - - -%% Binary mode -transform_ldata(<<0:32, 0:32>>) -> - []; -transform_ldata([<<0:32, N:32, Sizes/binary>> | Datas]) -> - transform_ldata(N, Sizes, Datas, []); -%% List mode -transform_ldata([_,_,_,_,_,_,_,_|_] = L0) -> - {0, L1} = get_uint32(L0), - {N, L2} = get_uint32(L1), - transform_ldata(N, L2, []). - -%% List mode -transform_ldata(0, List, Sizes) -> - transform_ldata(0, List, reverse(Sizes), []); -transform_ldata(N, L0, Sizes) -> - {Size, L1} = get_uint64(L0), - transform_ldata(N-1, L1, [Size | Sizes]). - -%% Binary mode -transform_ldata(1, <<0:64>>, <<>>, R) -> - reverse(R, [eof]); -transform_ldata(1, <<Size:64>>, Data, R) - when byte_size(Data) =:= Size -> - reverse(R, [Data]); -transform_ldata(N, <<0:64, Sizes/binary>>, [<<>> | Datas], R) -> - transform_ldata(N-1, Sizes, Datas, [eof | R]); -transform_ldata(N, <<Size:64, Sizes/binary>>, [Data | Datas], R) - when byte_size(Data) =:= Size -> - transform_ldata(N-1, Sizes, Datas, [Data | R]); -%% List mode -transform_ldata(0, [], [], R) -> - reverse(R); -transform_ldata(0, List, [0 | Sizes], R) -> - transform_ldata(0, List, Sizes, [eof | R]); -transform_ldata(0, List, [Size | Sizes], R) -> - {Front, Rear} = lists_split(List, Size), - transform_ldata(0, Rear, Sizes, [Front | R]). - -lists_split(List, 0) when is_list(List) -> - {[], List}; -lists_split(List, N) when is_list(List), is_integer(N), N < 0 -> - erlang:error(badarg, [List, N]); -lists_split(List, N) when is_list(List), is_integer(N) -> - case lists_split(List, N, []) of - premature_end_of_list -> - erlang:error(badarg, [List, N]); - Result -> - Result +make_symlink(Existing, New) -> + try + make_soft_link_nif(encode_path(Existing), encode_path(New)) + catch + error:badarg -> {error, badarg} end. -lists_split(List, 0, Rev) -> - {reverse(Rev), List}; -lists_split([], _, _) -> - premature_end_of_list; -lists_split([Hd | Tl], N, Rev) -> - lists_split(Tl, N-1, [Hd | Rev]). - -%% We KNOW that lists:reverse/2 is a BIF. - -reverse(X) -> lists:reverse(X, []). -reverse(L, T) -> lists:reverse(L, T). +altname(Path) -> + try altname_nif(encode_path(Path)) of + {ok, RawPath} -> {ok, decode_path(RawPath)}; + Other -> Other + catch + error:badarg -> {error, badarg} + end. -% Will add zero termination too -% The 'EXIT' tuple from a bad argument will eventually generate an error -% in list_to_binary, which is caught and generates the {error,badarg} return -pathname(File) -> - (catch prim_file:internal_name2native(File)). +list_dir_nif(_Path) -> + erlang:nif_error(undef). +read_link_nif(_Path) -> + erlang:nif_error(undef). +read_info_nif(_Path, _FollowLinks) -> + erlang:nif_error(undef). +make_hard_link_nif(_Existing, _New) -> + erlang:nif_error(undef). +make_soft_link_nif(_Existing, _New) -> + erlang:nif_error(undef). +rename_nif(_Source, _Destination) -> + erlang:nif_error(undef). +make_dir_nif(_Path) -> + erlang:nif_error(undef). +del_file_nif(_Path) -> + erlang:nif_error(undef). +del_dir_nif(_Path) -> + erlang:nif_error(undef). +get_device_cwd_nif(_DevicePath) -> + erlang:nif_error(undef). +set_cwd_nif(_Path) -> + erlang:nif_error(undef). +get_cwd_nif() -> + erlang:nif_error(undef). +altname_nif(_Path) -> + erlang:nif_error(undef). +%% +%% General helper functions. +%% -%% proplist:get_value/3 -plgv(K, [{K, V}|_], _) -> V; -plgv(K, [_|KVs], D) -> plgv(K, KVs, D); -plgv(_, [], D) -> D. +%% We know for certain that lists:reverse/2 is a BIF, so it's safe to use it +%% even though this module is preloaded. +reverse_list(List) -> lists:reverse(List, []). + +proplist_get_value(_Key, [], Default) -> + Default; +proplist_get_value(Key, [{Key, Value} | _Rest], _Default) -> + Value; +proplist_get_value(Key, [Key | _Rest], _Default) -> + true; +proplist_get_value(Key, [_Other | Rest], Default) -> + proplist_get_value(Key, Rest, Default). + +encode_path(Path) -> + prim_file:internal_name2native(Path). +decode_path(NativePath) when is_binary(NativePath) -> + prim_file:internal_native2name(NativePath). + +is_path_translatable(Path) when is_list(Path) -> + true; +is_path_translatable(Path) -> + prim_file:is_translatable(Path). -%% %% We don't actually want this here +%% %% We want to use posix time in all prim but erl_prim_loader makes that tricky %% It is probably needed to redo the whole erl_prim_loader -from_seconds(Seconds, posix) when is_integer(Seconds) -> +from_posix_seconds(Seconds, posix) when is_integer(Seconds) -> Seconds; -from_seconds(Seconds, universal) when is_integer(Seconds) -> +from_posix_seconds(Seconds, universal) when is_integer(Seconds) -> erlang:posixtime_to_universaltime(Seconds); -from_seconds(Seconds, local) when is_integer(Seconds) -> +from_posix_seconds(Seconds, local) when is_integer(Seconds) -> erlang:universaltime_to_localtime(erlang:posixtime_to_universaltime(Seconds)). -to_seconds(Seconds, posix) when is_integer(Seconds) -> +to_posix_seconds(Seconds, posix) when is_integer(Seconds) -> Seconds; -to_seconds({_,_} = Datetime, universal) -> +to_posix_seconds({_,_} = Datetime, universal) -> erlang:universaltime_to_posixtime(Datetime); -to_seconds({_,_} = Datetime, local) -> +to_posix_seconds({_,_} = Datetime, local) -> erlang:universaltime_to_posixtime(erlang:localtime_to_universaltime(Datetime)). diff --git a/erts/preloaded/src/prim_inet.erl b/erts/preloaded/src/prim_inet.erl index 61f727e8a4..f1d938c9a4 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2016. All Rights Reserved. +%% Copyright Ericsson AB 2000-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -29,9 +29,9 @@ -export([open/3, open/4, fdopen/4, fdopen/5, close/1]). -export([bind/3, listen/1, listen/2, peeloff/2]). -export([connect/3, connect/4, async_connect/4]). --export([accept/1, accept/2, async_accept/2]). +-export([accept/1, accept/2, accept/3, async_accept/2]). -export([shutdown/2]). --export([send/2, send/3, sendto/4, sendmsg/3]). +-export([send/2, send/3, sendto/4, sendmsg/3, sendfile/4]). -export([recv/2, recv/3, async_recv/3]). -export([unrecv/2]). -export([recvfrom/2, recvfrom/3]). @@ -49,9 +49,15 @@ -include("inet_sctp.hrl"). -include("inet_int.hrl"). -%-define(DEBUG, 1). +%%%-define(DEBUG, 1). -ifdef(DEBUG). --define(DBG_FORMAT(Format, Args), (io:format((Format), (Args)))). +-define( + DBG_FORMAT(Format, Args), + begin + %% io:format((Format), (Args)), + erlang:display(lists:flatten(io_lib:format((Format), (Args)))), + ok + end). -else. -define(DBG_FORMAT(Format, Args), ok). -endif. @@ -150,39 +156,96 @@ shutdown_1(S, How) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% close(S) when is_port(S) -> + ?DBG_FORMAT("prim_inet:close(~p)~n", [S]), case getopt(S, linger) of {ok,{true,0}} -> close_port(S); - _ -> - case subscribe(S, [subs_empty_out_q]) of - {ok, [{subs_empty_out_q,N}]} when N > 0 -> - close_pend_loop(S, N); %% wait for pending output to be sent - _ -> - close_port(S) - end + {ok,{true,T}} -> + %% Wait for T seconds for pending output to be sent + %% + %% Note that this handling of Linger may look ok, + %% but sweeps some problems under the rug since + %% there are OS buffers that may have remaining data + %% after the inet driver has emptied its buffers. + %% But Linger for nonblocking sockets is broken + %% anyway on all OS:es, according to hearsay, + %% and is a contradiction in itself. + %% We have hereby done our best... + %% + Tref = erlang:start_timer(T * 1000, self(), close_port), + close_pend_loop(S, Tref, undefined); + _ -> % Regard this as {ok,{false,_}} + case subscribe(S, [subs_empty_out_q]) of + {ok, [{subs_empty_out_q,N}]} when N > 0 -> + %% Wait for pending output to be sent + DefaultT = 180000, % Arbitrary system timeout 3 min + Tref = erlang:start_timer(DefaultT, self(), close_port), + close_pend_loop(S, Tref, N); + _ -> + %% Subscribe failed or empty out q - give up or done + close_port(S) + end end. -close_pend_loop(S, N) -> +close_pend_loop(S, Tref, N) -> + ?DBG_FORMAT("prim_inet:close_pend_loop(~p, _, ~p)~n", [S,N]), receive - {empty_out_q,S} -> - close_port(S) + {timeout,Tref,_} -> % Linger timeout + ?DBG_FORMAT("prim_inet:close_pend_loop(~p, _, _) timeout~n", [S]), + close_port(S); + {empty_out_q,S} when N =/= undefined -> + ?DBG_FORMAT( + "prim_inet:close_pend_loop(~p, _, _) empty_out_q~n", [S]), + close_port(S, Tref) after ?INET_CLOSE_TIMEOUT -> case getstat(S, [send_pend]) of {ok, [{send_pend,N1}]} -> + ?DBG_FORMAT( + "prim_inet:close_pend_loop(~p, _, _) send_pend ~p~n", + [S,N1]), if - N1 =:= N -> - close_port(S); - true -> - close_pend_loop(S, N1) + N1 =:= 0 -> + %% Empty outq - done + close_port(S, Tref); + N =:= undefined -> + %% Within linger time - wait some more + close_pend_loop(S, Tref, N); + N1 =:= N -> + %% Inactivity - give up + close_port(S, Tref); + true -> + %% Still moving - wait some more + close_pend_loop(S, Tref, N) end; - _ -> - close_port(S) - end + _Stat -> + %% Failed getstat - give up + ?DBG_FORMAT( + "prim_inet:close_pend_loop(~p, _, _) getstat ~p~n", + [S,_Stat]), + close_port(S, Tref) + end end. + +close_port(S, Tref) -> + ?DBG_FORMAT("prim_inet:close_port(~p, _)~n", [S]), + case erlang:cancel_timer(Tref) of + false -> + receive + {timeout,Tref,_} -> + ok + end; + _N -> + ok + end, + close_port(S). +%% close_port(S) -> - catch erlang:port_close(S), - receive {'EXIT',S,_} -> ok after 0 -> ok end. + ?DBG_FORMAT("prim_inet:close_port(~p)~n", [S]), + _Closed = (catch erlang:port_close(S)), + receive {'EXIT',S,_} -> ok after 0 -> ok end, + ?DBG_FORMAT("prim_inet:close_port(~p) ~p~n", [S,_Closed]), + ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @@ -307,7 +370,7 @@ async_connect0(S, Addr, Time) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% -%% ACCEPT(insock() [,Timeout] ) -> {ok,insock()} | {error, Reason} +%% ACCEPT(insock() [,Timeout][,FamilyOpts] ) -> {ok,insock()} | {error, Reason} %% %% accept incoming connection on listen socket %% if timeout is given: @@ -315,6 +378,8 @@ async_connect0(S, Addr, Time) -> %% 0 -> immediate accept (poll) %% > 0 -> wait for timeout ms for accept if no accept then %% return {error, timeout} +%% FamilyOpts are address family specific options to copy from +%% listen socket to accepted socket %% %% ASYNC_ACCEPT(insock(), Timeout) %% @@ -325,17 +390,22 @@ async_connect0(S, Addr, Time) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% For TCP sockets only. %% -accept(L) -> accept0(L, -1). +accept(L) -> accept0(L, -1, []). -accept(L, infinity) -> accept0(L, -1); -accept(L, Time) -> accept0(L, Time). +accept(L, infinity) -> accept0(L, -1, []); +accept(L, FamilyOpts) when is_list(FamilyOpts) -> accept0(L, -1, FamilyOpts); +accept(L, Time) -> accept0(L, Time, []). -accept0(L, Time) when is_port(L), is_integer(Time) -> +accept(L, infinity, FamilyOpts) -> accept0(L, -1, FamilyOpts); +accept(L, Time, FamilyOpts) -> accept0(L, Time, FamilyOpts). + +accept0(L, Time, FamilyOpts) + when is_port(L), is_integer(Time), is_list(FamilyOpts) -> case async_accept(L, Time) of {ok, Ref} -> receive {inet_async, L, Ref, {ok,S}} -> - accept_opts(L, S); + accept_opts(L, S, FamilyOpts); {inet_async, L, Ref, Error} -> Error end; @@ -343,25 +413,22 @@ accept0(L, Time) when is_port(L), is_integer(Time) -> end. %% setup options from listen socket on the connected socket -accept_opts(L, S) -> - case getopts(L, [active, nodelay, keepalive, delay_send, priority, tos]) of +accept_opts(L, S, FamilyOpts) -> + case + getopts( + L, + [active, nodelay, keepalive, delay_send, priority] + ++ FamilyOpts) + of {ok, Opts} -> - case setopts(S, Opts) of - ok -> - case getopts(L, [tclass]) of - {ok, []} -> - {ok, S}; - {ok, TClassOpts} -> - case setopts(S, TClassOpts) of - ok -> - {ok, S}; - Error -> close(S), Error - end - end; - Error -> close(S), Error - end; - Error -> - close(S), Error + case setopts(S, Opts) of + ok -> + {ok, S}; + Error1 -> + close(S), Error1 + end; + Error2 -> + close(S), Error2 end. async_accept(L, Time) -> @@ -420,23 +487,49 @@ peeloff(S, AssocId) -> %% be called directly -- use "sendmsg" instead: %% send(S, Data, OptList) when is_port(S), is_list(OptList) -> - ?DBG_FORMAT("prim_inet:send(~p, ~p)~n", [S,Data]), + ?DBG_FORMAT("prim_inet:send(~p, _, ~p)~n", [S,OptList]), try erlang:port_command(S, Data, OptList) of false -> % Port busy and nosuspend option passed ?DBG_FORMAT("prim_inet:send() -> {error,busy}~n", []), {error,busy}; true -> - receive - {inet_reply,S,Status} -> - ?DBG_FORMAT("prim_inet:send() -> ~p~n", [Status]), - Status - end + send_recv_reply(S, undefined) catch error:_Error -> ?DBG_FORMAT("prim_inet:send() -> {error,einval}~n", []), {error,einval} end. +send_recv_reply(S, Mref) -> + ReplyTimeout = + case Mref of + undefined -> + ?INET_CLOSE_TIMEOUT; + _ -> + infinity + end, + receive + {inet_reply,S,Status} -> + ?DBG_FORMAT( + "prim_inet:send_recv_reply(~p, _): inet_reply ~p~n", + [S,Status]), + case Mref of + undefined -> ok; + _ -> + demonitor(Mref, [flush]), + ok + end, + Status; + {'DOWN',Mref,_,_,_Reason} when Mref =/= undefined -> + ?DBG_FORMAT( + "prim_inet:send_recv_reply(~p, _) 'DOWN' ~p~n", + [S,_Reason]), + {error,closed} + after ReplyTimeout -> + send_recv_reply(S, monitor(port, S)) + end. + + send(S, Data) -> send(S, Data, []). @@ -500,6 +593,77 @@ sendmsg(S, #sctp_sndrcvinfo{}=SRI, Data) when is_port(S) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% +%% SENDFILE(outsock(), Fd, Offset, Length) -> {ok,BytesSent} | {error, Reason} +%% +%% send Length data bytes from a file handle, to a socket, starting at Offset +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% "sendfile" is for TCP: + +sendfile(S, FileHandle, Offset, Length) + when not is_port(S); + not is_binary(FileHandle); + not is_integer(Offset); + not is_integer(Length) -> + {error, badarg}; +sendfile(S, FileHandle, Offset, Length) -> + case erlang:port_info(S, connected) of + {connected, Pid} when Pid =:= self() -> + Uncork = sendfile_maybe_cork(S), + Result = sendfile_1(S, FileHandle, Offset, Length), + sendfile_maybe_uncork(S, Uncork), + Result; + {connected, Pid} when Pid =/= self() -> + {error, not_owner}; + _Other -> + {error, einval} + end. + +sendfile_maybe_cork(S) -> + case getprotocol(S) of + tcp -> + case getopts(S, [nopush]) of + {ok, [{nopush,false}]} -> + _ = setopts(S, [{nopush,true}]), + true; + _ -> + false + end; + _ -> false + end. + +sendfile_maybe_uncork(S, true) -> + _ = setopts(S, [{nopush,false}]), + ok; +sendfile_maybe_uncork(_, false) -> + ok. + +sendfile_1(S, FileHandle, Offset, 0) -> + sendfile_1(S, FileHandle, Offset, (1 bsl 63) - 1); +sendfile_1(_S, _FileHandle, Offset, Length) when + Offset < 0; Offset > ((1 bsl 63) - 1); + Length < 0; Length > ((1 bsl 63) - 1) -> + {error, einval}; +sendfile_1(S, FileHandle, Offset, Length) -> + Args = [FileHandle, + ?int64(Offset), + ?int64(Length)], + case ctl_cmd(S, ?TCP_REQ_SENDFILE, Args) of + {ok, []} -> + receive + {sendfile, S, {ok, SentLow, SentHigh}} -> + {ok, SentLow bor (SentHigh bsl 32)}; + {sendfile, S, {error, Reason}} -> + {error, Reason}; + {'EXIT', S, _Reason} -> + {error, closed} + end; + {error, Reason} -> + {error, Reason} + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% %% RECV(insock(), Length, [Timeout]) -> {ok,Data} | {error, Reason} %% %% receive Length data bytes from a socket @@ -567,7 +731,16 @@ recvfrom0(S, Length, Time) Ref = ?u16(R1,R0), receive % Success, UDP: + {inet_async, S, Ref, {ok, {[F | AddrData], AncData}}} -> + %% With ancillary data + case get_addr(F, AddrData) of + {{Family, _} = Addr, Data} when is_atom(Family) -> + {ok, {Addr, 0, AncData, Data}}; + {{IP, Port}, Data} -> + {ok, {IP, Port, AncData, Data}} + end; {inet_async, S, Ref, {ok, [F | AddrData]}} -> + %% Without ancillary data case get_addr(F, AddrData) of {{Family, _} = Addr, Data} when is_atom(Family) -> {ok, {Addr, 0, Data}}; @@ -808,9 +981,9 @@ chgopts(S, Opts) when is_port(S), is_list(Opts) -> getifaddrs(S) when is_port(S) -> case ctl_cmd(S, ?INET_REQ_GETIFADDRS, []) of - {ok, Data} -> - {ok, comp_ifaddrs(build_ifaddrs(Data), ktree_empty())}; - {error,enotsup} -> + {ok, Data} -> + {ok, comp_ifaddrs(build_ifaddrs(Data))}; + {error,enotsup} -> case getiflist(S) of {ok, IFs} -> {ok, getifaddrs_ifget(S, IFs)}; @@ -819,30 +992,75 @@ getifaddrs(S) when is_port(S) -> Err2 -> Err2 end. -%% Restructure interface properties per interface and remove duplicates - -comp_ifaddrs([{If,Opts}|IfOpts], T) -> - case ktree_is_defined(If, T) of - true -> - OptSet = comp_ifaddrs_add(ktree_get(If, T), Opts), - comp_ifaddrs(IfOpts, ktree_update(If, OptSet, T)); - false -> - OptSet = comp_ifaddrs_add(ktree_empty(), Opts), - comp_ifaddrs(IfOpts, ktree_insert(If, OptSet, T)) - end; -comp_ifaddrs([], T) -> - [{If,ktree_keys(ktree_get(If, T))} || If <- ktree_keys(T)]. - -comp_ifaddrs_add(OptSet, [Opt|Opts]) -> - case ktree_is_defined(Opt, OptSet) of - true - when element(1, Opt) =:= flags; - element(1, Opt) =:= hwaddr -> - comp_ifaddrs_add(OptSet, Opts); - _ -> - comp_ifaddrs_add(ktree_insert(Opt, undefined, OptSet), Opts) +%% Restructure interface properties per interface + +comp_ifaddrs(IfOpts) -> + comp_ifaddrs(IfOpts, ktree_empty()). +%% +comp_ifaddrs([{If,[{flags,Flags}|Opts]}|IfOpts], IfT) -> + case ktree_is_defined(If, IfT) of + true -> + comp_ifaddrs( + IfOpts, + ktree_update( + If, + comp_ifaddrs_flags(Flags, Opts, ktree_get(If, IfT)), + IfT)); + false -> + comp_ifaddrs( + IfOpts, + ktree_insert( + If, + comp_ifaddrs_flags(Flags, Opts, ktree_empty()), + IfT)) end; -comp_ifaddrs_add(OptSet, []) -> OptSet. +comp_ifaddrs([], IfT) -> + comp_ifaddrs_2(ktree_keys(IfT), IfT). + +comp_ifaddrs_flags(Flags, Opts, FlagsT) -> + case ktree_is_defined(Flags, FlagsT) of + true -> + ktree_update( + Flags, + rev(Opts, ktree_get(Flags, FlagsT)), + FlagsT); + false -> + ktree_insert(Flags, rev(Opts), FlagsT) + end. + +comp_ifaddrs_2([If|Ifs], IfT) -> + FlagsT = ktree_get(If, IfT), + [{If,comp_ifaddrs_3(ktree_keys(FlagsT), FlagsT)} + | comp_ifaddrs_2(Ifs, IfT)]; +comp_ifaddrs_2([], _IfT) -> + []. +%% +comp_ifaddrs_3([Flags|FlagsL], FlagsT) -> + [{flags,Flags}|hwaddr_last(rev(ktree_get(Flags, FlagsT)))] + ++ hwaddr_last(comp_ifaddrs_3(FlagsL, FlagsT)); +comp_ifaddrs_3([], _FlagsT) -> + []. + +%% Place hwaddr last to look more like legacy emulation +hwaddr_last(Opts) -> + hwaddr_last(Opts, Opts, []). +%% +hwaddr_last([{hwaddr,_} = Opt|Opts], L, R) -> + hwaddr_last(Opts, L, [Opt|R]); +hwaddr_last([_|Opts], L, R) -> + hwaddr_last(Opts, L, R); +hwaddr_last([], L, []) -> + L; +hwaddr_last([], L, R) -> + rev(hwaddr_last(L, []), rev(R)). +%% +hwaddr_last([{hwaddr,_}|Opts], R) -> + hwaddr_last(Opts, R); +hwaddr_last([Opt|Opts], R) -> + hwaddr_last(Opts, [Opt|R]); +hwaddr_last([], R) -> + R. + %% Legacy emulation of getifaddrs @@ -850,21 +1068,19 @@ getifaddrs_ifget(_, []) -> []; getifaddrs_ifget(S, [IF|IFs]) -> case ifget(S, IF, [flags]) of {ok,[{flags,Flags}]=FlagsVals} -> - BroadOpts = - case member(broadcast, Flags) of - true -> - [broadaddr,hwaddr]; - false -> - [hwaddr] - end, - P2POpts = - case member(pointtopoint, Flags) of - true -> - [dstaddr|BroadOpts]; - false -> - BroadOpts - end, - getifaddrs_ifget(S, IFs, IF, FlagsVals, [addr,netmask|P2POpts]); + GetOpts = + case member(pointtopoint, Flags) of + true -> + [dstaddr,hwaddr]; + false -> + case member(broadcast, Flags) of + true -> + [broadaddr,hwaddr]; + false -> + [hwaddr] + end + end, + getifaddrs_ifget(S, IFs, IF, FlagsVals, [addr,netmask|GetOpts]); _ -> getifaddrs_ifget(S, IFs, IF, [], [addr,netmask,hwaddr]) end. @@ -1207,7 +1423,13 @@ enc_opt(recbuf) -> ?INET_OPT_RCVBUF; enc_opt(priority) -> ?INET_OPT_PRIORITY; enc_opt(tos) -> ?INET_OPT_TOS; enc_opt(tclass) -> ?INET_OPT_TCLASS; +enc_opt(recvtos) -> ?INET_OPT_RECVTOS; +enc_opt(recvtclass) -> ?INET_OPT_RECVTCLASS; +enc_opt(pktoptions) -> ?INET_OPT_PKTOPTIONS; +enc_opt(ttl) -> ?INET_OPT_TTL; +enc_opt(recvttl) -> ?INET_OPT_RECVTTL; enc_opt(nodelay) -> ?TCP_OPT_NODELAY; +enc_opt(nopush) -> ?TCP_OPT_NOPUSH; enc_opt(multicast_if) -> ?UDP_OPT_MULTICAST_IF; enc_opt(multicast_ttl) -> ?UDP_OPT_MULTICAST_TTL; enc_opt(multicast_loop) -> ?UDP_OPT_MULTICAST_LOOP; @@ -1234,6 +1456,7 @@ enc_opt(netns) -> ?INET_LOPT_NETNS; enc_opt(show_econnreset) -> ?INET_LOPT_TCP_SHOW_ECONNRESET; enc_opt(line_delimiter) -> ?INET_LOPT_LINE_DELIM; enc_opt(raw) -> ?INET_OPT_RAW; +enc_opt(bind_to_device) -> ?INET_OPT_BIND_TO_DEVICE; % Names of SCTP opts: enc_opt(sctp_rtoinfo) -> ?SCTP_OPT_RTOINFO; enc_opt(sctp_associnfo) -> ?SCTP_OPT_ASSOCINFO; @@ -1268,6 +1491,12 @@ dec_opt(?INET_OPT_PRIORITY) -> priority; dec_opt(?INET_OPT_TOS) -> tos; dec_opt(?INET_OPT_TCLASS) -> tclass; dec_opt(?TCP_OPT_NODELAY) -> nodelay; +dec_opt(?TCP_OPT_NOPUSH) -> nopush; +dec_opt(?INET_OPT_RECVTOS) -> recvtos; +dec_opt(?INET_OPT_RECVTCLASS) -> recvtclass; +dec_opt(?INET_OPT_PKTOPTIONS) -> pktoptions; +dec_opt(?INET_OPT_TTL) -> ttl; +dec_opt(?INET_OPT_RECVTTL) -> recvttl; dec_opt(?UDP_OPT_MULTICAST_IF) -> multicast_if; dec_opt(?UDP_OPT_MULTICAST_TTL) -> multicast_ttl; dec_opt(?UDP_OPT_MULTICAST_LOOP) -> multicast_loop; @@ -1294,6 +1523,7 @@ dec_opt(?INET_LOPT_NETNS) -> netns; dec_opt(?INET_LOPT_TCP_SHOW_ECONNRESET) -> show_econnreset; dec_opt(?INET_LOPT_LINE_DELIM) -> line_delimiter; dec_opt(?INET_OPT_RAW) -> raw; +dec_opt(?INET_OPT_BIND_TO_DEVICE) -> bind_to_device; dec_opt(I) when is_integer(I) -> undefined. @@ -1342,7 +1572,13 @@ type_opt_1(recbuf) -> int; type_opt_1(priority) -> int; type_opt_1(tos) -> int; type_opt_1(tclass) -> int; +type_opt_1(recvtos) -> bool; +type_opt_1(recvtclass) -> bool; +type_opt_1(pktoptions) -> opts; +type_opt_1(ttl) -> int; +type_opt_1(recvttl) -> bool; type_opt_1(nodelay) -> bool; +type_opt_1(nopush) -> bool; type_opt_1(ipv6_v6only) -> bool; %% multicast type_opt_1(multicast_ttl) -> int; @@ -1395,6 +1631,7 @@ type_opt_1(packet_size) -> uint; type_opt_1(read_packets) -> uint; type_opt_1(netns) -> binary; type_opt_1(show_econnreset) -> bool; +type_opt_1(bind_to_device) -> binary; %% %% SCTP options (to be set). If the type is a record type, the corresponding %% record signature is returned, otherwise, an "elementary" type tag @@ -1847,6 +2084,11 @@ dec_value(binary,[L0,L1,L2,L3|List]) -> Len = ?i32(L0,L1,L2,L3), {X,T}=split(Len,List), {list_to_binary(X),T}; +dec_value(opts, [L0,L1,L2,L3|List]) -> + Len = ?u32(L0,L1,L2,L3), + {X,T} = split(Len, List), + Opts = dec_opt_val(X), + {Opts,T}; dec_value(Types, List) when is_tuple(Types) -> {L,T} = dec_value_tuple(Types, List, 1, []), {list_to_tuple(L),T}; @@ -2415,7 +2657,7 @@ get_addrs([F|Addrs]) -> [Addr|get_addrs(Rest)]. get_addr(?INET_AF_LOCAL, [N|Addr]) -> - {A,Rest} = lists:split(N, Addr), + {A,Rest} = split(N, Addr), {{local,iolist_to_binary(A)},Rest}; get_addr(?INET_AF_UNSPEC, Rest) -> {{unspec,<<>>},Rest}; diff --git a/erts/preloaded/src/prim_zip.erl b/erts/preloaded/src/prim_zip.erl index b1ddbbe173..ca5cfec0e3 100644 --- a/erts/preloaded/src/prim_zip.erl +++ b/erts/preloaded/src/prim_zip.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -74,8 +74,8 @@ open(FilterFun, FilterAcc, F) when is_function(FilterFun, 2) -> throw(Reason); throw:InternalReason -> {error, InternalReason}; - Class:Reason -> - erlang:error(erlang:raise(Class, Reason, erlang:get_stacktrace())) + Class:Reason:Stk -> + erlang:error(erlang:raise(Class, Reason, Stk)) end; open(_, _, _) -> {error, einval}. @@ -89,9 +89,9 @@ do_open(FilterFun, FilterAcc, F) -> {PrimZip2, FilterAcc2} = get_central_dir(PrimZip, FilterFun, FilterAcc), {ok, PrimZip2, FilterAcc2} catch - Class:Reason -> + Class:Reason:Stk -> _ = close(PrimZip), - erlang:error(erlang:raise(Class, Reason, erlang:get_stacktrace())) + erlang:error(erlang:raise(Class, Reason, Stk)) end. %% iterate over all files in a zip archive @@ -106,8 +106,8 @@ foldl(FilterFun, FilterAcc, #primzip{files = Files} = PrimZip) throw(Reason); throw:InternalReason -> {error, InternalReason}; - Class:Reason -> - erlang:error(erlang:raise(Class, Reason, erlang:get_stacktrace())) + Class:Reason:Stk -> + erlang:error(erlang:raise(Class, Reason, Stk)) end; foldl(_, _, _) -> {error, einval}. diff --git a/erts/preloaded/src/zlib.erl b/erts/preloaded/src/zlib.erl index fa0f28c5c3..6f53e67901 100644 --- a/erts/preloaded/src/zlib.erl +++ b/erts/preloaded/src/zlib.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2016. All Rights Reserved. +%% Copyright Ericsson AB 2003-2018. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,19 +20,31 @@ -module(zlib). --export([open/0,close/1,deflateInit/1,deflateInit/2,deflateInit/6, - deflateSetDictionary/2,deflateReset/1,deflateParams/3, - deflate/2,deflate/3,deflateEnd/1, - inflateInit/1,inflateInit/2,inflateSetDictionary/2, - inflateSync/1,inflateReset/1,inflate/2,inflateEnd/1, - inflateChunk/1, inflateChunk/2, - setBufSize/2,getBufSize/1, - crc32/1,crc32/2,crc32/3,adler32/2,adler32/3,getQSize/1, - crc32_combine/4,adler32_combine/4, - compress/1,uncompress/1,zip/1,unzip/1, - gzip/1,gunzip/1]). - --export_type([zstream/0, zlevel/0, zwindowbits/0, zmemlevel/0, zstrategy/0]). +-export([open/0,close/1,set_controlling_process/2, + deflateInit/1,deflateInit/2,deflateInit/6, + deflateSetDictionary/2,deflateReset/1,deflateParams/3, + deflate/2,deflate/3,deflateEnd/1, + inflateInit/1,inflateInit/2,inflateInit/3, + inflateSetDictionary/2,inflateGetDictionary/1, inflateReset/1, + inflate/2,inflate/3,inflateEnd/1, + inflateChunk/2,inflateChunk/1, + safeInflate/2, + setBufSize/2,getBufSize/1, + crc32/1,crc32/2,crc32/3,adler32/2,adler32/3, + crc32_combine/4,adler32_combine/4, + compress/1,uncompress/1,zip/1,unzip/1, + gzip/1,gunzip/1]). + +-export([on_load/0]). + +%% These are soft-deprecated until OTP 21. +% -deprecated([inflateChunk/1, inflateChunk/2, +% getBufSize/1, setBufSize/2, +% crc32/1,crc32/2,crc32/3,adler32/2,adler32/3, +% crc32_combine/4,adler32_combine/4]). + +-export_type([zstream/0, zflush/0, zlevel/0, zwindowbits/0, zmemlevel/0, + zstrategy/0]). %% flush argument encoding -define(Z_NO_FLUSH, 0). @@ -55,115 +67,88 @@ %% deflate compression method -define(Z_DEFLATED, 8). --define(Z_NULL, 0). - -define(MAX_WBITS, 15). -%% gzip defs (rfc 1952) - --define(ID1, 16#1f). --define(ID2, 16#8b). - --define(FTEXT, 16#01). --define(FHCRC, 16#02). --define(FEXTRA, 16#04). --define(FNAME, 16#08). --define(FCOMMENT, 16#10). --define(RESERVED, 16#E0). - --define(OS_MDDOS, 0). --define(OS_AMIGA, 1). --define(OS_OPENVMS, 2). --define(OS_UNIX, 3). --define(OS_VMCMS, 4). --define(OS_ATARI, 5). --define(OS_OS2, 6). --define(OS_MAC, 7). --define(OS_ZSYS, 8). --define(OS_CPM, 9). --define(OS_TOP20, 10). --define(OS_NTFS, 11). --define(OS_QDOS, 12). --define(OS_ACORN, 13). --define(OS_UNKNOWN,255). - --define(DEFLATE_INIT, 1). --define(DEFLATE_INIT2, 2). --define(DEFLATE_SETDICT, 3). --define(DEFLATE_RESET, 4). --define(DEFLATE_END, 5). --define(DEFLATE_PARAMS, 6). --define(DEFLATE, 7). - --define(INFLATE_INIT, 8). --define(INFLATE_INIT2, 9). --define(INFLATE_SETDICT, 10). --define(INFLATE_SYNC, 11). --define(INFLATE_RESET, 12). --define(INFLATE_END, 13). --define(INFLATE, 14). --define(INFLATE_CHUNK, 25). - --define(CRC32_0, 15). --define(CRC32_1, 16). --define(CRC32_2, 17). - --define(SET_BUFSZ, 18). --define(GET_BUFSZ, 19). --define(GET_QSIZE, 20). - --define(ADLER32_1, 21). --define(ADLER32_2, 22). - --define(CRC32_COMBINE, 23). --define(ADLER32_COMBINE, 24). +-define(DEFAULT_MEMLEVEL, 8). +-define(DEFAULT_WBITS, 15). + +-define(EOS_BEHAVIOR_ERROR, 0). +-define(EOS_BEHAVIOR_RESET, 1). +-define(EOS_BEHAVIOR_CUT, 2). + +%% Chunk sizes are hardcoded on account of them screwing with the +%% predictability of the system. zlib is incapable of trapping so we need to +%% ensure that it never operates on any significant amount of data. +-define(DEFLATE_IN_CHUNKSIZE, 8 bsl 10). +-define(DEFLATE_OUT_CHUNKSIZE, 8 bsl 10). +-define(INFLATE_IN_CHUNKSIZE, 8 bsl 10). +-define(INFLATE_OUT_CHUNKSIZE, 16 bsl 10). %%------------------------------------------------------------------------ -%% Main data types of the file --type zstream() :: port(). +%% Public data types. +-type zstream() :: reference(). +-type zflush() :: 'none' | 'sync' | 'full' | 'finish'. -%% Auxiliary data types of the file --type zlevel() :: 'none' | 'default' | 'best_compression' | 'best_speed' - | 0..9. --type zmethod() :: 'deflated'. +-type zlevel() :: + 'none' | 'default' | 'best_compression' | 'best_speed' | 0..9. +-type zstrategy() :: 'default' | 'filtered' | 'huffman_only' | 'rle'. + +-type zmemlevel() :: 1..9. -type zwindowbits() :: -15..-8 | 8..47. --type zmemlevel() :: 1..9. --type zstrategy() :: 'default' | 'filtered' | 'huffman_only' | 'rle'. + +%% Private data types. + +-type zmethod() :: 'deflated'. + +-record(zlib_opts, { + stream :: zstream() | 'undefined', + method :: function(), + input_chunk_size :: pos_integer(), + output_chunk_size :: pos_integer(), + flush :: non_neg_integer() + }). %%------------------------------------------------------------------------ -%% open a z_stream +on_load() -> + case erlang:load_nif(atom_to_list(?MODULE), 0) of + ok -> ok + end. + -spec open() -> zstream(). open() -> - open_port({spawn, "zlib_drv"}, [binary]). + open_nif(). +open_nif() -> + erlang:nif_error(undef). -%% close and release z_stream -spec close(Z) -> 'ok' when Z :: zstream(). close(Z) -> - try - true = port_close(Z), - receive %In case the caller is the owner and traps exits - {'EXIT',Z,_} -> ok - after 0 -> ok - end - catch _:_ -> erlang:error(badarg) - end. + close_nif(Z). +close_nif(_Z) -> + erlang:nif_error(undef). + +-spec set_controlling_process(Z, Pid) -> 'ok' when + Z :: zstream(), + Pid :: pid(). +set_controlling_process(Z, Pid) -> + set_controller_nif(Z, Pid). +set_controller_nif(_Z, _Pid) -> + erlang:nif_error(undef). -spec deflateInit(Z) -> 'ok' when Z :: zstream(). deflateInit(Z) -> - call(Z, ?DEFLATE_INIT, <<?Z_DEFAULT_COMPRESSION:32>>). + deflateInit(Z, default). -spec deflateInit(Z, Level) -> 'ok' when Z :: zstream(), Level :: zlevel(). deflateInit(Z, Level) -> - call(Z, ?DEFLATE_INIT, <<(arg_level(Level)):32>>). + deflateInit(Z, Level, deflated, ?DEFAULT_WBITS, ?DEFAULT_MEMLEVEL, default). --spec deflateInit(Z, Level, Method, - WindowBits, MemLevel, Strategy) -> 'ok' when +-spec deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) -> 'ok' when Z :: zstream(), Level :: zlevel(), Method :: zmethod(), @@ -171,31 +156,48 @@ deflateInit(Z, Level) -> MemLevel :: zmemlevel(), Strategy :: zstrategy(). deflateInit(Z, Level, Method, WindowBits, MemLevel, Strategy) -> - call(Z, ?DEFLATE_INIT2, <<(arg_level(Level)):32, - (arg_method(Method)):32, - (arg_bitsz(WindowBits)):32, - (arg_mem(MemLevel)):32, - (arg_strategy(Strategy)):32>>). + deflateInit_nif(Z, + arg_level(Level), + arg_method(Method), + arg_bitsz(WindowBits), + arg_mem(MemLevel), + arg_strategy(Strategy)). +deflateInit_nif(_Z, _Level, _Method, _WindowBits, _MemLevel, _Strategy) -> + erlang:nif_error(undef). -spec deflateSetDictionary(Z, Dictionary) -> Adler32 when Z :: zstream(), Dictionary :: iodata(), - Adler32 :: integer(). + Adler32 :: non_neg_integer(). deflateSetDictionary(Z, Dictionary) -> - call(Z, ?DEFLATE_SETDICT, Dictionary). + deflateSetDictionary_nif(Z, Dictionary). +deflateSetDictionary_nif(_Z, _Dictionary) -> + erlang:nif_error(undef). -spec deflateReset(Z) -> 'ok' when Z :: zstream(). deflateReset(Z) -> - call(Z, ?DEFLATE_RESET, []). + deflateReset_nif(Z). +deflateReset_nif(_Z) -> + erlang:nif_error(undef). -spec deflateParams(Z, Level, Strategy) -> ok when Z :: zstream(), Level :: zlevel(), Strategy :: zstrategy(). -deflateParams(Z, Level, Strategy) -> - call(Z, ?DEFLATE_PARAMS, <<(arg_level(Level)):32, - (arg_strategy(Strategy)):32>>). +deflateParams(Z, Level0, Strategy0) -> + Level = arg_level(Level0), + Strategy = arg_strategy(Strategy0), + Progress = deflate(Z, <<>>, sync), + case deflateParams_nif(Z, Level, Strategy) of + ok -> + save_progress(Z, deflate, Progress), + ok; + Other -> + Other + end. +deflateParams_nif(_Z, _Level, _Strategy) -> + erlang:nif_error(undef). -spec deflate(Z, Data) -> Compressed when Z :: zstream(), @@ -207,181 +209,252 @@ deflate(Z, Data) -> -spec deflate(Z, Data, Flush) -> Compressed when Z :: zstream(), Data :: iodata(), - Flush :: none | sync | full | finish, + Flush :: zflush(), Compressed :: iolist(). deflate(Z, Data, Flush) -> - try port_command(Z, Data) of - true -> - _ = call(Z, ?DEFLATE, <<(arg_flush(Flush)):32>>), - collect(Z) - catch - error:_Err -> - flush(Z), - erlang:error(badarg) - end. + Progress = restore_progress(Z, deflate), + enqueue_input(Z, Data), + append_iolist(Progress, dequeue_all_chunks(Z, deflate_opts(Flush))). + +deflate_opts(Flush) -> + #zlib_opts{ + method = fun deflate_nif/4, + input_chunk_size = ?DEFLATE_IN_CHUNKSIZE, + output_chunk_size = ?DEFLATE_OUT_CHUNKSIZE, + flush = arg_flush(Flush) + }. + +deflate_nif(_Z, _InputChSize, _OutputChSize, _Flush) -> + erlang:nif_error(undef). -spec deflateEnd(Z) -> 'ok' when Z :: zstream(). deflateEnd(Z) -> - call(Z, ?DEFLATE_END, []). + deflateEnd_nif(Z). +deflateEnd_nif(_Z) -> + erlang:nif_error(undef). -spec inflateInit(Z) -> 'ok' when Z :: zstream(). inflateInit(Z) -> - call(Z, ?INFLATE_INIT, []). + inflateInit(Z, ?DEFAULT_WBITS). -spec inflateInit(Z, WindowBits) -> 'ok' when Z :: zstream(), WindowBits :: zwindowbits(). -inflateInit(Z, WindowBits) -> - call(Z, ?INFLATE_INIT2, <<(arg_bitsz(WindowBits)):32>>). +inflateInit(Z, WindowBits) -> + inflateInit(Z, WindowBits, cut). + +-spec inflateInit(Z, WindowBits, EoSBehavior) -> 'ok' when + Z :: zstream(), + WindowBits :: zwindowbits(), + EoSBehavior :: error | reset | cut. +inflateInit(Z, WindowBits, EoSBehavior) -> + inflateInit_nif(Z, arg_bitsz(WindowBits), arg_eos_behavior(EoSBehavior)). +inflateInit_nif(_Z, _WindowBits, _EoSBehavior) -> + erlang:nif_error(undef). -spec inflateSetDictionary(Z, Dictionary) -> 'ok' when Z :: zstream(), Dictionary :: iodata(). -inflateSetDictionary(Z, Dictionary) -> - call(Z, ?INFLATE_SETDICT, Dictionary). +inflateSetDictionary(Z, Dictionary) -> + inflateSetDictionary_nif(Z, Dictionary). +inflateSetDictionary_nif(_Z, _Dictionary) -> + erlang:nif_error(undef). --spec inflateSync(zstream()) -> 'ok'. -inflateSync(Z) -> - call(Z, ?INFLATE_SYNC, []). +-spec inflateGetDictionary(Z) -> Dictionary when + Z :: zstream(), + Dictionary :: binary(). +inflateGetDictionary(Z) -> + case inflateGetDictionary_nif(Z) of + Dictionary when is_binary(Dictionary) -> + Dictionary; + not_supported -> + erlang:error(enotsup) + end. +inflateGetDictionary_nif(_Z) -> + erlang:nif_error(undef). -spec inflateReset(Z) -> 'ok' when Z :: zstream(). -inflateReset(Z) -> - call(Z, ?INFLATE_RESET, []). +inflateReset(Z) -> + inflateReset_nif(Z). +inflateReset_nif(_Z) -> + erlang:nif_error(undef). -spec inflate(Z, Data) -> Decompressed when Z :: zstream(), Data :: iodata(), Decompressed :: iolist(). inflate(Z, Data) -> - try port_command(Z, Data) of - true -> - _ = call(Z, ?INFLATE, <<?Z_NO_FLUSH:32>>), - collect(Z) - catch - error:_Err -> - flush(Z), - erlang:error(badarg) + inflate(Z, Data, []). + +-spec inflate(Z, Data, Options) -> Decompressed when + Z :: zstream(), + Data :: iodata(), + Options :: list({exception_on_need_dict, boolean()}), + Decompressed :: iolist() | + {need_dictionary, + Adler32 :: non_neg_integer(), + Output :: iolist()}. +inflate(Z, Data, Options) -> + enqueue_input(Z, Data), + Result = dequeue_all_chunks(Z, inflate_opts()), + case proplist_get_value(Options, exception_on_need_dict, true) of + true -> exception_on_need_dict(Z, Result); + false -> Result end. +inflate_nif(_Z, _InputChSize, _OutputChSize, _Flush) -> + erlang:nif_error(undef). + +inflate_opts() -> + #zlib_opts{ + method = fun inflate_nif/4, + input_chunk_size = ?INFLATE_IN_CHUNKSIZE, + output_chunk_size = ?INFLATE_OUT_CHUNKSIZE, + flush = arg_flush(none) + }. + -spec inflateChunk(Z, Data) -> Decompressed | {more, Decompressed} when Z :: zstream(), Data :: iodata(), Decompressed :: iolist(). inflateChunk(Z, Data) -> - try port_command(Z, Data) of - true -> - inflateChunk(Z) - catch - error:_Err -> - flush(Z), - erlang:error(badarg) - end. + enqueue_input(Z, Data), + inflateChunk(Z). -spec inflateChunk(Z) -> Decompressed | {more, Decompressed} when Z :: zstream(), Decompressed :: iolist(). inflateChunk(Z) -> - Status = call(Z, ?INFLATE_CHUNK, []), - Data = receive - {Z, {data, Bin}} -> - Bin - after 0 -> - [] - end, - - case Status of - Good when (Good == ok) orelse (Good == stream_end) -> - Data; - inflate_has_more -> - {more, Data} - end. + Opts0 = inflate_opts(), + Opts = Opts0#zlib_opts { output_chunk_size = getBufSize(Z) }, + + Result0 = dequeue_next_chunk(Z, Opts), + Result1 = exception_on_need_dict(Z, Result0), + yield_inflateChunk(Z, Result1). + +yield_inflateChunk(_Z, {continue, Output}) -> + {more, lists:flatten(Output)}; +yield_inflateChunk(_Z, {finished, Output}) -> + lists:flatten(Output). + +exception_on_need_dict(Z, {need_dictionary, Adler, Output}) -> + Progress = restore_progress(Z, inflate), + save_progress(Z, inflate, append_iolist(Progress, Output)), + erlang:error({need_dictionary, Adler}); +exception_on_need_dict(Z, {Mark, Output}) -> + Progress = restore_progress(Z, inflate), + {Mark, append_iolist(Progress, Output)}; +exception_on_need_dict(Z, Output) when is_list(Output); is_binary(Output) -> + Progress = restore_progress(Z, inflate), + append_iolist(Progress, Output). + +-spec safeInflate(Z, Data) -> Result when + Z :: zstream(), + Data :: iodata(), + Result :: {continue, Output :: iolist()} | + {finished, Output :: iolist()} | + {need_dictionary, + Adler32 :: non_neg_integer(), + Output :: iolist()}. +safeInflate(Z, Data) -> + enqueue_input(Z, Data), + dequeue_next_chunk(Z, inflate_opts()). -spec inflateEnd(Z) -> 'ok' when Z :: zstream(). inflateEnd(Z) -> - call(Z, ?INFLATE_END, []). + inflateEnd_nif(Z). +inflateEnd_nif(_Z) -> + erlang:nif_error(undef). -spec setBufSize(Z, Size) -> 'ok' when Z :: zstream(), Size :: non_neg_integer(). -setBufSize(Z, Size) -> - call(Z, ?SET_BUFSZ, <<Size:32>>). +setBufSize(Z, Size) when is_integer(Size), Size > 16, Size < (1 bsl 24) -> + setBufSize_nif(Z, Size); +setBufSize(_Z, _Size) -> + erlang:error(badarg). +setBufSize_nif(_Z, _Size) -> + erlang:nif_error(undef). --spec getBufSize(Z) -> Size when - Z :: zstream(), - Size :: non_neg_integer(). +-spec getBufSize(Z) -> non_neg_integer() when + Z :: zstream(). getBufSize(Z) -> - call(Z, ?GET_BUFSZ, []). + getBufSize_nif(Z). +getBufSize_nif(_Z) -> + erlang:nif_error(undef). -spec crc32(Z) -> CRC when Z :: zstream(), - CRC :: integer(). + CRC :: non_neg_integer(). crc32(Z) -> - call(Z, ?CRC32_0, []). + crc32_nif(Z). +crc32_nif(_Z) -> + erlang:nif_error(undef). -spec crc32(Z, Data) -> CRC when Z :: zstream(), Data :: iodata(), - CRC :: integer(). -crc32(Z, Data) -> - call(Z, ?CRC32_1, Data). + CRC :: non_neg_integer(). +crc32(Z, Data) when is_reference(Z) -> + erlang:crc32(Data); +crc32(_Z, _Data) -> + erlang:error(badarg). -spec crc32(Z, PrevCRC, Data) -> CRC when Z :: zstream(), - PrevCRC :: integer(), + PrevCRC :: non_neg_integer(), Data :: iodata(), - CRC :: integer(). -crc32(Z, CRC, Data) -> - call(Z, ?CRC32_2, [<<CRC:32>>, Data]). + CRC :: non_neg_integer(). +crc32(Z, CRC, Data) when is_reference(Z) -> + erlang:crc32(CRC, Data); +crc32(_Z, _CRC, _Data) -> + erlang:error(badarg). --spec adler32(Z, Data) -> CheckSum when +-spec crc32_combine(Z, CRC1, CRC2, Size2) -> CRC when Z :: zstream(), - Data :: iodata(), - CheckSum :: integer(). -adler32(Z, Data) -> - call(Z, ?ADLER32_1, Data). + CRC :: non_neg_integer(), + CRC1 :: non_neg_integer(), + CRC2 :: non_neg_integer(), + Size2 :: non_neg_integer(). +crc32_combine(Z, CRC1, CRC2, Size2) when is_reference(Z) -> + erlang:crc32_combine(CRC1, CRC2, Size2); +crc32_combine(_Z, _CRC1, _CRC2, _Size2) -> + erlang:error(badarg). --spec adler32(Z, PrevAdler, Data) -> CheckSum when +-spec adler32(Z, Data) -> CheckSum when Z :: zstream(), - PrevAdler :: integer(), Data :: iodata(), - CheckSum :: integer(). -adler32(Z, Adler, Data) when is_integer(Adler) -> - call(Z, ?ADLER32_2, [<<Adler:32>>, Data]); -adler32(_Z, _Adler, _Data) -> + CheckSum :: non_neg_integer(). +adler32(Z, Data) when is_reference(Z) -> + erlang:adler32(Data); +adler32(_Z, _Data) -> erlang:error(badarg). --spec crc32_combine(Z, CRC1, CRC2, Size2) -> CRC when +-spec adler32(Z, PrevAdler, Data) -> CheckSum when Z :: zstream(), - CRC :: integer(), - CRC1 :: integer(), - CRC2 :: integer(), - Size2 :: integer(). -crc32_combine(Z, CRC1, CRC2, Len2) - when is_integer(CRC1), is_integer(CRC2), is_integer(Len2) -> - call(Z, ?CRC32_COMBINE, <<CRC1:32, CRC2:32, Len2:32>>); -crc32_combine(_Z, _CRC1, _CRC2, _Len2) -> + PrevAdler :: non_neg_integer(), + Data :: iodata(), + CheckSum :: non_neg_integer(). +adler32(Z, Adler, Data) when is_reference(Z) -> + erlang:adler32(Adler, Data); +adler32(_Z, _Adler, _Data) -> erlang:error(badarg). -spec adler32_combine(Z, Adler1, Adler2, Size2) -> Adler when Z :: zstream(), - Adler :: integer(), - Adler1 :: integer(), - Adler2 :: integer(), - Size2 :: integer(). -adler32_combine(Z, Adler1, Adler2, Len2) - when is_integer(Adler1), is_integer(Adler2), is_integer(Len2) -> - call(Z, ?ADLER32_COMBINE, <<Adler1:32, Adler2:32, Len2:32>>); -adler32_combine(_Z, _Adler1, _Adler2, _Len2) -> + Adler :: non_neg_integer(), + Adler1 :: non_neg_integer(), + Adler2 :: non_neg_integer(), + Size2 :: non_neg_integer(). +adler32_combine(Z, Adler1, Adler2, Size2) when is_reference(Z) -> + erlang:adler32_combine(Adler1, Adler2, Size2); +adler32_combine(_Z, _Adler1, _Adler2, _Size2) -> erlang:error(badarg). --spec getQSize(zstream()) -> non_neg_integer(). -getQSize(Z) -> - call(Z, ?GET_QSIZE, []). - %% compress/uncompress zlib with header -spec compress(Data) -> Compressed when Data :: iodata(), @@ -389,13 +462,13 @@ getQSize(Z) -> compress(Data) -> Z = open(), Bs = try - deflateInit(Z, default), - B = deflate(Z, Data, finish), - deflateEnd(Z), - B - after - close(Z) - end, + deflateInit(Z, default), + B = deflate(Z, Data, finish), + deflateEnd(Z), + B + after + close(Z) + end, iolist_to_binary(Bs). -spec uncompress(Data) -> Decompressed when @@ -407,14 +480,14 @@ uncompress(Data) -> if Size >= 8 -> Z = open(), - Bs = try - inflateInit(Z), - B = inflate(Z, Data), - inflateEnd(Z), - B - after - close(Z) - end, + Bs = try + inflateInit(Z), + B = inflate(Z, Data), + inflateEnd(Z), + B + after + close(Z) + end, iolist_to_binary(Bs); true -> erlang:error(data_error) @@ -431,13 +504,13 @@ uncompress(Data) -> zip(Data) -> Z = open(), Bs = try - deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default), - B = deflate(Z, Data, finish), - deflateEnd(Z), - B - after - close(Z) - end, + deflateInit(Z, default, deflated, -?MAX_WBITS, 8, default), + B = deflate(Z, Data, finish), + deflateEnd(Z), + B + after + close(Z) + end, iolist_to_binary(Bs). -spec unzip(Data) -> Decompressed when @@ -446,28 +519,28 @@ zip(Data) -> unzip(Data) -> Z = open(), Bs = try - inflateInit(Z, -?MAX_WBITS), - B = inflate(Z, Data), - inflateEnd(Z), - B - after - close(Z) - end, + inflateInit(Z, -?MAX_WBITS), + B = inflate(Z, Data), + inflateEnd(Z), + B + after + close(Z) + end, iolist_to_binary(Bs). - + -spec gzip(Data) -> Compressed when Data :: iodata(), Compressed :: binary(). gzip(Data) -> Z = open(), Bs = try - deflateInit(Z, default, deflated, 16+?MAX_WBITS, 8, default), - B = deflate(Z, Data, finish), - deflateEnd(Z), - B - after - close(Z) - end, + deflateInit(Z, default, deflated, 16+?MAX_WBITS, 8, default), + B = deflate(Z, Data, finish), + deflateEnd(Z), + B + after + close(Z) + end, iolist_to_binary(Bs). -spec gunzip(Data) -> Decompressed when @@ -476,92 +549,155 @@ gzip(Data) -> gunzip(Data) -> Z = open(), Bs = try - inflateInit(Z, 16+?MAX_WBITS), - B = inflate(Z, Data), - inflateEnd(Z), - B - after - close(Z) - end, + inflateInit(Z, 16+?MAX_WBITS, reset), + B = inflate(Z, Data), + inflateEnd(Z), + B + after + close(Z) + end, iolist_to_binary(Bs). --spec collect(zstream()) -> iolist(). -collect(Z) -> - collect(Z, []). - --spec collect(zstream(), iolist()) -> iolist(). -collect(Z, Acc) -> - receive - {Z, {data, Bin}} -> - collect(Z, [Bin|Acc]) - after 0 -> - reverse(Acc) +-spec dequeue_all_chunks(Z, Opts) -> Result when + Z :: zstream(), + Opts :: #zlib_opts{}, + Result :: {need_dictionary, integer(), iolist()} | + iolist(). +dequeue_all_chunks(Z, Opts) -> + dequeue_all_chunks_1(Z, Opts, []). +dequeue_all_chunks_1(Z, Opts, Output) -> + case dequeue_next_chunk(Z, Opts) of + {need_dictionary, _, _} = NeedDict -> + NeedDict; + {continue, Chunk} -> + dequeue_all_chunks_1(Z, Opts, append_iolist(Output, Chunk)); + {finished, Chunk} -> + append_iolist(Output, Chunk) end. --spec flush(zstream()) -> 'ok'. -flush(Z) -> - receive - {Z, {data,_}} -> - flush(Z) - after 0 -> - ok +-spec dequeue_next_chunk(Z, Opts) -> Result when + Z :: zstream(), + Opts :: #zlib_opts{}, + Result :: {need_dictionary, integer(), iolist()} | + {continue, iolist()} | + {finished, iolist()}. +dequeue_next_chunk(Z, Opts) -> + Method = Opts#zlib_opts.method, + IChSz = Opts#zlib_opts.input_chunk_size, + OChSz = Opts#zlib_opts.output_chunk_size, + Flush = Opts#zlib_opts.flush, + Method(Z, IChSz, OChSz, Flush). + +-spec append_iolist(IO, D) -> iolist() when + IO :: iodata(), + D :: iodata(). +append_iolist([], D) when is_list(D) -> D; +append_iolist([], D) -> [D]; +append_iolist(IO, []) -> IO; +append_iolist(IO, [D]) -> [IO, D]; +append_iolist(IO, D) -> [IO, D]. + +%% inflate/2 and friends are documented as throwing an error on Z_NEED_DICT +%% rather than simply returning something to that effect, and deflateParams/3 +%% may flush behind the scenes. This requires us to stow away our current +%% progress in the handle and resume from that point on our next call. +%% +%% Generally speaking this is either a refc binary or nothing at all, so it's +%% pretty cheap. + +-spec save_progress(Z, Kind, Output) -> ok when + Z :: zstream(), + Kind :: inflate | deflate, + Output :: iolist(). +save_progress(Z, Kind, Output) -> + ok = setStash_nif(Z, {Kind, Output}). + +-spec restore_progress(Z, Kind) -> iolist() when + Z :: zstream(), + Kind :: inflate | deflate. +restore_progress(Z, Kind) -> + case getStash_nif(Z) of + {ok, {Kind, Output}} -> + ok = clearStash_nif(Z), + Output; + empty -> + [] end. - -arg_flush(none) -> ?Z_NO_FLUSH; + +-spec clearStash_nif(Z) -> ok when + Z :: zstream(). +clearStash_nif(_Z) -> + erlang:nif_error(undef). + +-spec setStash_nif(Z, Term) -> ok when + Z :: zstream(), + Term :: term(). +setStash_nif(_Z, _Term) -> + erlang:nif_error(undef). + +-spec getStash_nif(Z) -> {ok, term()} | empty when + Z :: zstream(). +getStash_nif(_Z) -> + erlang:nif_error(undef). + +%% The 'proplists' module isn't preloaded so we can't rely on its existence. +proplist_get_value([], _Name, DefVal) -> DefVal; +proplist_get_value([{Name, Value} | _Opts], Name, _DefVal) -> Value; +proplist_get_value([_Head | Opts], Name, DefVal) -> + proplist_get_value(Opts, Name, DefVal). + +arg_flush(none) -> ?Z_NO_FLUSH; %% ?Z_PARTIAL_FLUSH is deprecated in zlib -- deliberately not included. -arg_flush(sync) -> ?Z_SYNC_FLUSH; -arg_flush(full) -> ?Z_FULL_FLUSH; -arg_flush(finish) -> ?Z_FINISH; -arg_flush(_) -> erlang:error(badarg). +arg_flush(sync) -> ?Z_SYNC_FLUSH; +arg_flush(full) -> ?Z_FULL_FLUSH; +arg_flush(finish) -> ?Z_FINISH; +arg_flush(_) -> erlang:error(bad_flush_mode). arg_level(none) -> ?Z_NO_COMPRESSION; arg_level(best_speed) -> ?Z_BEST_SPEED; arg_level(best_compression) -> ?Z_BEST_COMPRESSION; arg_level(default) -> ?Z_DEFAULT_COMPRESSION; arg_level(Level) when is_integer(Level), Level >= 0, Level =< 9 -> Level; -arg_level(_) -> erlang:error(badarg). - +arg_level(_) -> erlang:error(bad_compression_level). + arg_strategy(filtered) -> ?Z_FILTERED; arg_strategy(huffman_only) -> ?Z_HUFFMAN_ONLY; arg_strategy(rle) -> ?Z_RLE; arg_strategy(default) -> ?Z_DEFAULT_STRATEGY; -arg_strategy(_) -> erlang:error(badarg). +arg_strategy(_) -> erlang:error(bad_compression_strategy). arg_method(deflated) -> ?Z_DEFLATED; -arg_method(_) -> erlang:error(badarg). +arg_method(_) -> erlang:error(bad_compression_method). + +arg_eos_behavior(error) -> ?EOS_BEHAVIOR_ERROR; +arg_eos_behavior(reset) -> ?EOS_BEHAVIOR_RESET; +arg_eos_behavior(cut) -> ?EOS_BEHAVIOR_CUT; +arg_eos_behavior(_) -> erlang:error(bad_eos_behavior). -spec arg_bitsz(zwindowbits()) -> zwindowbits(). arg_bitsz(Bits) when is_integer(Bits) andalso - ((8 =< Bits andalso Bits < 48) orelse - (-15 =< Bits andalso Bits =< -8)) -> + ((8 =< Bits andalso Bits < 48) orelse + (-15 =< Bits andalso Bits =< -8)) -> Bits; -arg_bitsz(_) -> erlang:error(badarg). +arg_bitsz(_) -> erlang:error(bad_windowbits). -spec arg_mem(zmemlevel()) -> zmemlevel(). arg_mem(Level) when is_integer(Level), 1 =< Level, Level =< 9 -> Level; -arg_mem(_) -> erlang:error(badarg). - -call(Z, Cmd, Arg) -> - try port_control(Z, Cmd, Arg) of - [0|Res] -> list_to_atom(Res); - [1|Res] -> - flush(Z), - erlang:error(list_to_atom(Res)); - [2,A,B,C,D] -> - (A bsl 24)+(B bsl 16)+(C bsl 8)+D; - [3,A,B,C,D] -> - erlang:error({need_dictionary,(A bsl 24)+(B bsl 16)+(C bsl 8)+D}); - [4, _, _, _, _] -> - inflate_has_more - catch - error:badarg -> %% Rethrow loses port_control from stacktrace. - erlang:error(badarg) - end. +arg_mem(_) -> erlang:error(bad_memlevel). -reverse(X) -> - reverse(X, []). +-spec enqueue_input(Z, IOData) -> ok when + Z :: zstream(), + IOData :: iodata(). +enqueue_input(Z, IOData) -> + enqueue_input_1(Z, erlang:iolist_to_iovec(IOData)). + +enqueue_input_1(_Z, []) -> + ok; +enqueue_input_1(Z, IOVec) -> + case enqueue_nif(Z, IOVec) of + {continue, Remainder} -> enqueue_input_1(Z, Remainder); + ok -> ok + end. -reverse([H|T], Y) -> - reverse(T, [H|Y]); -reverse([], X) -> - X. +enqueue_nif(_Z, _IOVec) -> + erlang:nif_error(undef). |