diff options
Diffstat (limited to 'erts/preloaded')
36 files changed, 4463 insertions, 2990 deletions
diff --git a/erts/preloaded/Makefile b/erts/preloaded/Makefile index 31fdeb96c5..e8935d4410 100644 --- a/erts/preloaded/Makefile +++ b/erts/preloaded/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2008-2009. All Rights Reserved. +# Copyright Ericsson AB 2008-2016. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. -# -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# 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% # diff --git a/erts/preloaded/ebin/erl_prim_loader.beam b/erts/preloaded/ebin/erl_prim_loader.beam Binary files differindex 10138cab22..0f5f5036f0 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 differnew file mode 100644 index 0000000000..6017112dac --- /dev/null +++ b/erts/preloaded/ebin/erl_tracer.beam diff --git a/erts/preloaded/ebin/erlang.beam b/erts/preloaded/ebin/erlang.beam Binary files differindex 7a3d94dd16..bd8cc7d7e0 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 differnew file mode 100644 index 0000000000..c899b69a2c --- /dev/null +++ b/erts/preloaded/ebin/erts_code_purger.beam 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..9490a56758 --- /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 9b364d8b21..15c59de80a 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 differnew file mode 100644 index 0000000000..e650a6b5af --- /dev/null +++ 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 a190129b2c..858a9dc63e 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 621f036f33..0d194896c7 100644 --- a/erts/preloaded/ebin/otp_ring0.beam +++ b/erts/preloaded/ebin/otp_ring0.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..4ad1380d0b --- /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 6695f4cd15..2ae18846bf 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 5e9230826d..df611f2bb0 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 83230d42ae..4a345f8152 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 b3abba3223..4923cadbdc 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 7006764d96..07e7e97814 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 4ea2d41075..4333f6643a 100644 --- a/erts/preloaded/src/Makefile +++ b/erts/preloaded/src/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2008-2013. All Rights Reserved. +# Copyright Ericsson AB 2008-2018. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. -# -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# 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% # @@ -35,13 +36,18 @@ include $(ERL_TOP)/lib/kernel/vsn.mk PRE_LOADED_ERL_MODULES = \ erl_prim_loader \ init \ + prim_buffer \ prim_file \ prim_inet \ zlib \ prim_zip \ otp_ring0 \ + erts_code_purger \ erlang \ - erts_internal + erts_internal \ + erl_tracer \ + erts_literal_area_collector \ + erts_dirty_process_signal_handler PRE_LOADED_BEAM_MODULES = \ prim_eval @@ -68,7 +74,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) @@ -111,7 +117,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 211a60c930..9040199417 100644 --- a/erts/preloaded/src/add_abstract_code +++ b/erts/preloaded/src/add_abstract_code @@ -4,18 +4,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -27,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(). @@ -41,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/erl_prim_loader.erl b/erts/preloaded/src/erl_prim_loader.erl index 6b86a427ba..ae5f86e017 100644 --- a/erts/preloaded/src/erl_prim_loader.erl +++ b/erts/preloaded/src/erl_prim_loader.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -41,7 +42,7 @@ -include("inet_boot.hrl"). %% Public --export([start/3, set_path/1, get_path/0, get_file/1, get_files/2, +-export([start/0, set_path/1, get_path/0, get_file/1, list_dir/1, read_file_info/1, read_link_info/1, get_cwd/0, get_cwd/1]). %% Used by erl_boot_server @@ -49,30 +50,31 @@ prim_read_file_info/3, prim_get_cwd/2]). %% Used by escript and code --export([set_primary_archive/4, release_archives/0]). +-export([set_primary_archive/4]). + +%% Used by test suites +-export([purge_archive_cache/0]). + +%% Used by init and the code server. +-export([get_modules/2,get_modules/3, is_basename/1]). -include_lib("kernel/include/file.hrl"). -type host() :: atom(). -record(prim_state, {debug :: boolean(), - cache, primary_archive}). -type prim_state() :: #prim_state{}. -record(state, {loader :: 'efile' | 'inet', hosts = [] :: [host()], % hosts list (to boot from) - id, % not used any more? data :: 'noport' | port(), % data port etc - timeout :: timeout(), % idle timeout - %% Number of timeouts before archives are released - n_timeouts :: non_neg_integer(), - multi_get = false :: boolean(), + timeout :: timeout(), % idle timeout prim_state :: prim_state()}). % state for efile code loader --define(IDLE_TIMEOUT, 60000). %% tear inet connection after 1 minutes --define(N_TIMEOUTS, 6). %% release efile archive after 6 minutes +-define(EFILE_IDLE_TIMEOUT, (6*60*1000)). %purge archives +-define(INET_IDLE_TIMEOUT, (60*1000)). %tear down connection timeout %% Defines for inet as prim_loader -define(INET_FAMILY, inet). @@ -102,26 +104,13 @@ debug(#prim_state{debug = Deb}, Term) -> %%% Interface Functions. %%% -------------------------------------------------------- --spec start(Id, Loader, Hosts) -> +-spec start() -> {'ok', Pid} | {'error', What} when - Id :: term(), - Loader :: atom() | string(), - Hosts :: Host | [Host], - Host :: host(), Pid :: pid(), What :: term(). -start(Id, Pgm, Hosts) when is_atom(Hosts) -> - start(Id, Pgm, [Hosts]); -start(Id, Pgm0, Hosts) -> - Pgm = if - is_atom(Pgm0) -> - atom_to_list(Pgm0); - true -> - Pgm0 - end, +start() -> Self = self(), - Pid = spawn_link(fun() -> start_it(Pgm, Id, Self, Hosts) end), - register(erl_prim_loader, Pid), + Pid = spawn_link(fun() -> start_it(Self) end), receive {Pid,ok} -> {ok,Pid}; @@ -129,29 +118,41 @@ start(Id, Pgm0, Hosts) -> {error,Reason} end. -%% Hosts must be a list of form ['1.2.3.4' ...] -start_it("inet", Id, Pid, Hosts) -> +start_it(Parent) -> process_flag(trap_exit, true), - ?dbg(inet, {Id,Pid,Hosts}), + register(erl_prim_loader, self()), + Loader = case init:get_argument(loader) of + {ok,[[Loader0]]} -> + Loader0; + error -> + "efile" + end, + case Loader of + "efile" -> start_efile(Parent); + "inet" -> start_inet(Parent) + end. + +%% Hosts must be a list of form ['1.2.3.4' ...] +start_inet(Parent) -> + Hosts = case init:get_argument(hosts) of + {ok,[Hosts0]} -> Hosts0; + _ -> [] + end, AL = ipv4_list(Hosts), ?dbg(addresses, AL), {ok,Tcp} = find_master(AL), - init_ack(Pid), + init_ack(Parent), PS = prim_init(), State = #state {loader = inet, hosts = AL, - id = Id, data = Tcp, - timeout = ?IDLE_TIMEOUT, - n_timeouts = ?N_TIMEOUTS, + timeout = ?INET_IDLE_TIMEOUT, prim_state = PS}, - loop(State, Pid, []); + loop(State, Parent, []). -start_it("efile", Id, Pid, _Hosts) -> - process_flag(trap_exit, true), - {ok, Port} = prim_file:start(), +start_efile(Parent) -> %% 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 " @@ -159,20 +160,14 @@ start_it("efile", Id, Pid, _Hosts) -> erlang:display(Report), exit({error, invalid_current_directory}); _ -> - init_ack(Pid) + init_ack(Parent) end, - MultiGet = case erlang:system_info(thread_pool_size) of - 0 -> false; - _ -> true - end, PS = prim_init(), State = #state {loader = efile, - id = Id, - data = Port, - timeout = infinity, - multi_get = MultiGet, + data = noport, + timeout = ?EFILE_IDLE_TIMEOUT, prim_state = PS}, - loop(State, Pid, []). + loop(State, Parent, []). init_ack(Pid) -> Pid ! {self(),ok}, @@ -197,20 +192,6 @@ get_file(File) when is_atom(File) -> get_file(File) -> check_file_result(get_file, File, request({get_file,File})). --spec get_files([{atom(), string()}], - fun((atom(),binary(),string()) -> 'ok' | {'error', atom()})) -> - 'ok' | {'error', atom()}. -get_files(ModFiles, Fun) -> - case request({get_files,{ModFiles,Fun}}) of - E = {error,_M} -> - E; - {error,Reason,M} -> - check_file_result(get_files, M, {error,Reason}), - {error,M}; - ok -> - ok - end. - -spec list_dir(Dir) -> {'ok', Filenames} | 'error' when Dir :: string(), Filenames :: [Filename :: string()]. @@ -249,10 +230,30 @@ set_primary_archive(File, ArchiveBin, FileInfo, ParserFun) when is_list(File), is_binary(ArchiveBin), is_record(FileInfo, file_info) -> request({set_primary_archive, File, ArchiveBin, FileInfo, ParserFun}). --spec release_archives() -> 'ok' | {'error', _}. +%% NOTE: Does not close the primary archive. Only closes all +%% open zip files kept in the cache. Should be called before an archive +%% file is to be removed (for example in the test suites). + +-spec purge_archive_cache() -> 'ok' | {'error', _}. +purge_archive_cache() -> + request(purge_archive_cache). + +-spec get_modules([module()], + fun((atom(), string(), binary()) -> + {'ok',any()} | {'error',any()})) -> + {'ok',{[any()],[any()]}}. + +get_modules(Modules, Fun) -> + request({get_modules,{Modules,Fun}}). + +-spec get_modules([module()], + fun((atom(), string(), binary()) -> + {'ok',any()} | {'error',any()}), + [string()]) -> + {'ok',{[any()],[any()]}}. -release_archives() -> - request(release_archives). +get_modules(Modules, Fun, Path) -> + request({get_modules,{Modules,Fun,Path}}). request(Req) -> Loader = whereis(erl_prim_loader), @@ -298,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) -> @@ -309,76 +314,63 @@ check_file_result(_, _, Other) -> %%% The main loop. %%% -------------------------------------------------------- -loop(State, Parent, Paths) -> +loop(St0, Parent, Paths) -> receive + {Pid,{set_path,NewPaths}} when is_pid(Pid) -> + Pid ! {self(),ok}, + loop(St0, Parent, to_strs(NewPaths)); {Pid,Req} when is_pid(Pid) -> - %% erlang:display(Req), - {Resp,State2,Paths2} = - case Req of - {set_path,NewPaths} -> - {ok,State,to_strs(NewPaths)}; - {get_path,_} -> - {{ok,Paths},State,Paths}; - {get_file,File} -> - {Res,State1} = handle_get_file(State, Paths, File), - {Res,State1,Paths}; - {get_files,{ModFiles,Fun}} -> - {Res,State1} = handle_get_files(State, ModFiles, Paths, Fun), - {Res,State1,Paths}; - {list_dir,Dir} -> - {Res,State1} = handle_list_dir(State, Dir), - {Res,State1,Paths}; - {read_file_info,File} -> - {Res,State1} = handle_read_file_info(State, File), - {Res,State1,Paths}; - {read_link_info,File} -> - {Res,State1} = handle_read_link_info(State, File), - {Res,State1,Paths}; - {get_cwd,[]} -> - {Res,State1} = handle_get_cwd(State, []), - {Res,State1,Paths}; - {get_cwd,[_]=Args} -> - {Res,State1} = handle_get_cwd(State, Args), - {Res,State1,Paths}; - {set_primary_archive,File,ArchiveBin,FileInfo,ParserFun} -> - {Res,State1} = - handle_set_primary_archive(State, File, - ArchiveBin, FileInfo, - ParserFun), - {Res,State1,Paths}; - release_archives -> - {Res,State1} = handle_release_archives(State), - {Res,State1,Paths}; - _Other -> - {ignore,State,Paths} - end, - if Resp =:= ignore -> ok; - true -> Pid ! {self(),Resp}, ok - end, - if - is_record(State2, state) -> - loop(State2, Parent, Paths2); - true -> - exit({bad_state, Req, State2}) + case handle_request(Req, Paths, St0) of + ignore -> + ok; + {Resp,#state{}=St1} -> + Pid ! {self(),Resp}, + loop(St1, Parent, Paths); + {_,State2,_} -> + exit({bad_state,Req,State2}) end; {'EXIT',Parent,W} -> - _State1 = handle_stop(State), + _ = handle_stop(St0), exit(W); {'EXIT',P,W} -> - State1 = handle_exit(State, P, W), - loop(State1, Parent, Paths); + St1 = handle_exit(St0, P, W), + loop(St1, Parent, Paths); _Message -> - loop(State, Parent, Paths) - after State#state.timeout -> - State1 = handle_timeout(State, Parent), - loop(State1, Parent, Paths) + loop(St0, Parent, Paths) + after St0#state.timeout -> + St1 = handle_timeout(St0, Parent), + loop(St1, Parent, Paths) + end. + +handle_request(Req, Paths, St0) -> + case Req of + {get_path,_} -> + {{ok,Paths},St0}; + {get_file,File} -> + handle_get_file(St0, Paths, File); + {get_modules,{Modules,Fun}} -> + handle_get_modules(St0, Modules, Fun, Paths); + {get_modules,{Modules,Fun,ModPaths}} -> + handle_get_modules(St0, Modules, Fun, ModPaths); + {list_dir,Dir} -> + handle_list_dir(St0, Dir); + {read_file_info,File} -> + handle_read_file_info(St0, File); + {read_link_info,File} -> + handle_read_link_info(St0, File); + {get_cwd,[]} -> + handle_get_cwd(St0, []); + {get_cwd,[_]=Args} -> + handle_get_cwd(St0, Args); + {set_primary_archive,File,ArchiveBin,FileInfo,ParserFun} -> + handle_set_primary_archive(St0, File, ArchiveBin, + FileInfo, ParserFun); + purge_archive_cache -> + handle_purge_archive_cache(St0); + _ -> + ignore end. -handle_get_files(State = #state{multi_get = true}, ModFiles, Paths, Fun) -> - ?SAFE2(efile_multi_get_file_from_port(State, ModFiles, Paths, Fun), State); -handle_get_files(State, _ModFiles, _Paths, _Fun) -> % no multi get - {{error,no_multi_get},State}. - handle_get_file(State = #state{loader = efile}, Paths, File) -> ?SAFE2(efile_get_file_from_port(State, File, Paths), State); handle_get_file(State = #state{loader = inet}, Paths, File) -> @@ -387,8 +379,9 @@ handle_get_file(State = #state{loader = inet}, Paths, File) -> handle_set_primary_archive(State= #state{loader = efile}, File, ArchiveBin, FileInfo, ParserFun) -> ?SAFE2(efile_set_primary_archive(State, File, ArchiveBin, FileInfo, ParserFun), State). -handle_release_archives(State= #state{loader = efile}) -> - ?SAFE2(efile_release_archives(State), State). +handle_purge_archive_cache(#state{loader = efile}=State) -> + prim_purge_cache(), + {ok,State}. handle_list_dir(State = #state{loader = efile}, Dir) -> ?SAFE2(efile_list_dir(State, Dir), State); @@ -411,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). @@ -429,53 +422,6 @@ handle_timeout(State = #state{loader = inet}, Parent) -> %%% Functions which handle efile as prim_loader (default). %%% -------------------------------------------------------- -%%% Reading many files in parallel is an optimization. -%%% See also comment in init.erl. - -%% -> {ok,State} | {{error,Module},State} | {{error,Reason,Module},State} -efile_multi_get_file_from_port(State, ModFiles, Paths, Fun) -> - Ref = make_ref(), - %% More than 200 processes is no gain. - Max = erlang:min(200, erlang:system_info(thread_pool_size)), - efile_multi_get_file_from_port2(ModFiles, 0, Max, State, Paths, Fun, Ref, ok). - -efile_multi_get_file_from_port2([MF | MFs], Out, Max, State, Paths, Fun, Ref, Ret) when Out < Max -> - Self = self(), - _Pid = spawn(fun() -> efile_par_get_file(Ref, State, MF, Paths, Self, Fun) end), - efile_multi_get_file_from_port2(MFs, Out+1, Max, State, Paths, Fun, Ref, Ret); -efile_multi_get_file_from_port2(MFs, Out, Max, _State, Paths, Fun, Ref, Ret) when Out > 0 -> - receive - {Ref, ok, State1} -> - efile_multi_get_file_from_port2(MFs, Out-1, Max, State1, Paths, Fun, Ref, Ret); - {Ref, {error,_Mod} = Error, State1} -> - efile_multi_get_file_from_port2(MFs, Out-1, Max, State1, Paths, Fun, Ref, Error); - {Ref, MF, {error,emfile,State1}} -> - %% Max can take negative values. Out cannot. - efile_multi_get_file_from_port2([MF | MFs], Out-1, Max-1, State1, Paths, Fun, Ref, Ret); - {Ref, {M,_F}, {error,Error,State1}} -> - efile_multi_get_file_from_port2(MFs, Out-1, 0, State1, Paths, Fun, Ref, {error,Error,M}) - end; -efile_multi_get_file_from_port2(_MFs, 0, _Max, State, _Paths, _Fun, _Ref, Ret) -> - {Ret,State}. - -efile_par_get_file(Ref, State, {Mod,File} = MF, Paths, Pid, Fun) -> - %% One port for each file read in "parallel": - case prim_file:start() of - {ok, Port} -> - Port0 = State#state.data, - State1 = State#state{data = Port}, - R = case efile_get_file_from_port(State1, File, Paths) of - {{error,Reason},State2} -> - {Ref,MF,{error,Reason,State2}}; - {{ok,BinFile,Full},State2} -> - %% Fun(...) -> ok | {error,Mod} - {Ref,Fun(Mod, BinFile, Full),State2#state{data=Port0}} - end, - prim_file:close(Port), - Pid ! R; - {error, Error} -> - Pid ! {Ref,MF,{error,Error,State}} - end. %% -> {{ok,BinFile,File},State} | {{error,Reason},State} efile_get_file_from_port(State, File, Paths) -> @@ -520,10 +466,6 @@ efile_set_primary_archive(#state{prim_state = PS} = State, File, FileInfo, ParserFun), {Res,State#state{prim_state = PS2}}. -efile_release_archives(#state{prim_state = PS} = State) -> - {Res, PS2} = prim_release_archives(PS), - {Res,State#state{prim_state = PS2}}. - efile_list_dir(#state{prim_state = PS} = State, Dir) -> {Res, PS2} = prim_list_dir(PS, Dir), {Res, State#state{prim_state = PS2}}. @@ -536,24 +478,127 @@ 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) -> +efile_timeout_handler(State, _Parent) -> + prim_purge_cache(), State. -efile_timeout_handler(#state{n_timeouts = N} = State, _Parent) -> - if - N =< 0 -> - {_Res, State2} = efile_release_archives(State), - State2#state{n_timeouts = ?N_TIMEOUTS}; - true -> - State#state{n_timeouts = N - 1} +%%% -------------------------------------------------------- +%%% Read and process severals modules in parallel. +%%% -------------------------------------------------------- + +handle_get_modules(#state{loader=efile}=St, Ms, Process, Paths) -> + Primary = (St#state.prim_state)#prim_state.primary_archive, + Res = case efile_any_archives(Paths, Primary) of + false -> + efile_get_mods_par(Ms, Process, Paths); + true -> + Get = fun efile_get_file_from_port/3, + gm_get_mods(St, Get, Ms, Process, Paths) + end, + {Res,St}; +handle_get_modules(#state{loader=inet}=St, Ms, Process, Paths) -> + Get = fun inet_get_file_from_port/3, + {gm_get_mods(St, Get, Ms, Process, Paths),St}. + +efile_get_mods_par(Ms, Process, Paths) -> + Self = self(), + Ref = make_ref(), + GmSpawn = fun() -> + efile_gm_spawn({Self,Ref}, Ms, Process, Paths) + end, + _ = spawn_link(GmSpawn), + N = length(Ms), + efile_gm_recv(N, Ref, [], []). + +efile_any_archives([H|T], Primary) -> + case name_split(Primary, H) of + {file,_} -> efile_any_archives(T, Primary); + {archive,_,_} -> true + end; +efile_any_archives([], _) -> + false. + +efile_gm_recv(0, _Ref, Succ, Fail) -> + {ok,{Succ,Fail}}; +efile_gm_recv(N, Ref, Succ, Fail) -> + receive + {Ref,Mod,{ok,Res}} -> + efile_gm_recv(N-1, Ref, [{Mod,Res}|Succ], Fail); + {Ref,Mod,{error,Res}} -> + efile_gm_recv(N-1, Ref, Succ, [{Mod,Res}|Fail]) + end. + +efile_gm_spawn(ParentRef, Ms, Process, Paths) -> + efile_gm_spawn_1(0, Ms, ParentRef, Process, Paths). + +efile_gm_spawn_1(N, Ms, ParentRef, Process, Paths) when N >= 32 -> + receive + {'DOWN',_,process,_,_} -> + efile_gm_spawn_1(N-1, Ms, ParentRef, Process, Paths) + end; +efile_gm_spawn_1(N, [M|Ms], ParentRef, Process, Paths) -> + Get = fun() -> efile_gm_get(Paths, M, ParentRef, Process) end, + _ = spawn_monitor(Get), + efile_gm_spawn_1(N+1, Ms, ParentRef, Process, Paths); +efile_gm_spawn_1(_, [], _, _, _) -> + ok. + +efile_gm_get(Paths, Mod, ParentRef, Process) -> + File = atom_to_list(Mod) ++ init:objfile_extension(), + efile_gm_get_1(Paths, File, Mod, ParentRef, Process). + +efile_gm_get_1([P|Ps], File0, Mod, {Parent,Ref}=PR, Process) -> + File = join(P, File0), + 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}}. + +gm_get_mods(St, Get, Ms, Process, Paths) -> + gm_get_mods(St, Get, Ms, Process, Paths, [], []). + +gm_get_mods(St, Get, [M|Ms], Process, Paths, Succ, Fail) -> + File = atom_to_list(M) ++ init:objfile_extension(), + case gm_arch_get(St, Get, M, File, Paths, Process) of + {ok,Res} -> + gm_get_mods(St, Get, Ms, Process, Paths, + [{M,Res}|Succ], Fail); + {error,Res} -> + gm_get_mods(St, Get, Ms, Process, Paths, + Succ, [{M,Res}|Fail]) + end; +gm_get_mods(_St, _Get, [], _, _, Succ, Fail) -> + {ok,{Succ,Fail}}. + +gm_arch_get(St, Get, Mod, File, Paths, Process) -> + case Get(St, File, Paths) of + {{error,_}=E,_} -> + E; + {{ok,Bin,Full},_} -> + gm_process(Mod, Full, Bin, Process) end. +gm_process(Mod, File, Bin, Process) -> + try Process(Mod, File, Bin) of + {ok,_}=Res -> Res; + {error,_}=Res -> Res; + Other -> {error,{bad_return,Other}} + catch + _:Error -> + {error,{crash,Error}} + end. + + %%% -------------------------------------------------------- %%% Functions which handle inet prim_loader %%% -------------------------------------------------------- @@ -693,7 +738,7 @@ inet_get_file_from_port1(_File, [], State) -> inet_send_and_rcv(Msg, Tag, State) when State#state.data =:= noport -> {ok,Tcp} = find_master(State#state.hosts), %% reconnect inet_send_and_rcv(Msg, Tag, State#state{data = Tcp, - timeout = ?IDLE_TIMEOUT}); + timeout = ?INET_IDLE_TIMEOUT}); inet_send_and_rcv(Msg, Tag, #state{data = Tcp, timeout = Timeout} = State) -> prim_inet:send(Tcp, term_to_binary(Msg)), receive @@ -818,32 +863,19 @@ prim_init() -> end, cache_new(#prim_state{debug = Deb}). -prim_release_archives(PS) -> - debug(PS, release_archives), - {Res, PS2} = prim_do_release_archives(PS, get(), []), - debug(PS2, {return, Res}), - {Res, PS2}. - -prim_do_release_archives(PS, [{ArchiveFile, DictVal} | KeyVals], Acc) -> - Res = - case DictVal of - {primary, _PrimZip, _FI, _ParserFun} -> - ok; % Keep primary archive - {Cache, _FI} -> - debug(PS, {release, cache, ArchiveFile}), - erase(ArchiveFile), - clear_cache(ArchiveFile, Cache) - end, - case Res of - ok -> - prim_do_release_archives(PS, KeyVals, Acc); - {error, Reason} -> - prim_do_release_archives(PS, KeyVals, [{ArchiveFile, Reason} | Acc]) - end; -prim_do_release_archives(PS, [], []) -> - {ok, PS#prim_state{primary_archive = undefined}}; -prim_do_release_archives(PS, [], Errors) -> - {{error, Errors}, PS#prim_state{primary_archive = undefined}}. +prim_purge_cache() -> + do_prim_purge_cache(get()). + +do_prim_purge_cache([{Key,Val}|T]) -> + case Val of + {Cache,_FI} -> + catch clear_cache(Key, Cache); + _ -> + ok + end, + do_prim_purge_cache(T); +do_prim_purge_cache([]) -> + ok. prim_set_primary_archive(PS, undefined, undefined, undefined, _ParserFun) -> debug(PS, {set_primary_archive, clean}), @@ -1286,70 +1318,62 @@ path_join([Path],Acc) -> path_join([Path|Paths],Acc) -> path_join(Paths,"/" ++ reverse(Path) ++ Acc). -name_split(ArchiveFile, File0) -> - File = absname(File0), - do_name_split(ArchiveFile, File). - -do_name_split(undefined, File) -> +name_split(undefined, File) -> %% Ignore primary archive - case string_split(File, init:archive_extension(), []) of + RevExt = reverse(init:archive_extension()), + case archive_split(File, RevExt, []) of no_split -> - %% Plain file {file, File}; - {split, _RevArchiveBase, RevArchiveFile, []} -> - %% Top dir in archive - ArchiveFile = reverse(RevArchiveFile), - {archive, ArchiveFile, []}; - {split, _RevArchiveBase, RevArchiveFile, [$/ | FileInArchive]} -> - %% File in archive - ArchiveFile = reverse(RevArchiveFile), - {archive, ArchiveFile, FileInArchive}; - {split, _RevArchiveBase, _RevArchiveFile, _FileInArchive} -> - %% False match. Assume plain file - {file, File} + Archive -> + Archive end; -do_name_split(ArchiveFile, File) -> +name_split(ArchiveFile, File0) -> %% Look first in primary archive - case string_match(real_path(File), ArchiveFile, []) of + File = absname(File0), + case string_match(real_path(File), ArchiveFile) of no_match -> %% Archive or plain file - do_name_split(undefined, File); - {match, _RevPrimArchiveFile, FileInArchive} -> + name_split(undefined, File); + {match, FileInArchive} -> %% Primary archive {archive, ArchiveFile, FileInArchive} end. -string_match([Char | File], [Char | Archive], RevTop) -> - string_match(File, Archive, [Char | RevTop]); -string_match([] = File, [], RevTop) -> - {match, RevTop, File}; -string_match([$/ | File], [], RevTop) -> - {match, RevTop, File}; -string_match(_File, _Archive, _RevTop) -> +string_match([Char | File], [Char | Archive]) -> + string_match(File, Archive); +string_match([] = File, []) -> + {match, File}; +string_match([$/ | File], []) -> + {match, File}; +string_match(_File, _Archive) -> no_match. -string_split([Char | File], [Char | Ext] = FullExt, RevTop) -> - RevTop2 = [Char | RevTop], - string_split2(File, Ext, RevTop, RevTop2, File, FullExt, RevTop2); -string_split([Char | File], Ext, RevTop) -> - string_split(File, Ext, [Char | RevTop]); -string_split([], _Ext, _RevTop) -> - no_split. - -string_split2([Char | File], [Char | Ext], RevBase, RevTop, SaveFile, SaveExt, SaveTop) -> - string_split2(File, Ext, RevBase, [Char | RevTop], SaveFile, SaveExt, SaveTop); -string_split2(File, [], RevBase, RevTop, _SaveFile, _SaveExt, _SaveTop) -> - {split, RevBase, RevTop, File}; -string_split2(_, _Ext, _RevBase, _RevTop, SaveFile, SaveExt, SaveTop) -> - string_split(SaveFile, SaveExt, SaveTop). +archive_split("/"++File, RevExt, Acc) -> + case is_prefix(RevExt, Acc) of + false -> + archive_split(File, RevExt, [$/|Acc]); + true -> + ArchiveFile = absname(reverse(Acc)), + {archive, ArchiveFile, File} + end; +archive_split([H|T], RevExt, Acc) -> + archive_split(T, RevExt, [H|Acc]); +archive_split([], RevExt, Acc) -> + case is_prefix(RevExt, Acc) of + false -> + no_split; + true -> + ArchiveFile = absname(reverse(Acc)), + {archive, ArchiveFile, []} + end. + +is_prefix([H|T1], [H|T2]) -> is_prefix(T1, T2); +is_prefix([_|_], _) -> false; +is_prefix([], _ ) -> true. %% Parse list of ipv4 addresses ipv4_list([H | T]) -> - IPV = if is_atom(H) -> ipv4_address(atom_to_list(H)); - is_list(H) -> ipv4_address(H); - true -> {error,einal} - end, - case IPV of + case ipv4_address(H) of {ok,IP} -> [IP | ipv4_list(T)]; _ -> ipv4_list(T) end; @@ -1414,8 +1438,6 @@ absname_vr([Drive, $\: | NameRest], _) -> %% Assumes normalized name pathtype(Name) when is_list(Name) -> case erlang:system_info(os_type) of - {ose, _} -> - unix_pathtype(Name); {unix, _} -> unix_pathtype(Name); {win32, _} -> diff --git a/erts/preloaded/src/erl_tracer.erl b/erts/preloaded/src/erl_tracer.erl new file mode 100644 index 0000000000..c810069d17 --- /dev/null +++ b/erts/preloaded/src/erl_tracer.erl @@ -0,0 +1,65 @@ +-module(erl_tracer). + +-export([enabled/3, trace/5, on_load/0]). + +-type tracee() :: port() | pid() | undefined. + +-type trace_tag_running_ports() :: in | out | in_exiting | out_exiting | out_exited. +-type trace_tag_running_procs() :: in | out | in_exiting | out_exiting | out_exited. +-type trace_tag_send() :: send | send_to_non_existing_process. +-type trace_tag_receive() :: 'receive'. +-type trace_tag_call() :: call | return_to | return_from | exception_from. +-type trace_tag_procs() :: spawn | spawned | exit | link | unlink + | getting_linked | getting_unlinked + | register | unregister. +-type trace_tag_ports() :: open | closed | link | unlink + | getting_linked | getting_unlinked. +-type trace_tag_gc() :: gc_minor_start | gc_minor_end + | gc_major_start | gc_major_end. + +-type trace_tag() :: trace_tag_send() + | trace_tag_receive() + | trace_tag_call() + | trace_tag_procs() + | trace_tag_ports() + | trace_tag_running_procs() + | trace_tag_running_ports() + | trace_tag_gc(). + +-type trace_opts() :: #{ extra => term(), match_spec_result => term(), + scheduler_id => non_neg_integer(), + timestamp => timestamp | cpu_timestamp | + monotonic | strict_monotonic }. +-type tracer_state() :: term(). + +on_load() -> + case erlang:load_nif(atom_to_list(?MODULE), 0) of + ok -> ok + end. + +%%% +%%% NIF placeholders +%%% + +%% This suppression is needed as trace_tag gets collapsed to atom() +-dialyzer({no_contracts, enabled/3}). + +-spec enabled(Tag :: trace_status, + TracerState :: tracer_state(), + Tracee :: tracee()) -> + trace | remove; + (Tag :: trace_tag() | seq_trace, + TracerState :: tracer_state(), + Tracee :: tracee()) -> + trace | discard. +enabled(_, _, _) -> + erlang:nif_error(nif_not_loaded). + +-spec trace(Tag :: trace_tag() | seq_trace, + TracerState :: tracer_state(), + Tracee :: tracee(), + Msg :: term(), + Opts :: trace_opts()) -> any(). + +trace(_, _, _, _, _) -> + erlang:nif_error(nif_not_loaded). diff --git a/erts/preloaded/src/erlang.erl b/erts/preloaded/src/erlang.erl index 611a568014..1ed6b6b284 100644 --- a/erts/preloaded/src/erlang.erl +++ b/erts/preloaded/src/erlang.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -30,25 +31,26 @@ -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]). -export([integer_to_list/2]). -export([integer_to_binary/2]). --export([flush_monitor_message/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]). + +-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([hash/2]). +-deprecated([get_stacktrace/0,now/0]). %% Get rid of autoimports of spawn to avoid clashes with ourselves. -compile({no_auto_import,[spawn_link/1]}). @@ -58,12 +60,43 @@ -compile({no_auto_import,[spawn_opt/5]}). -export_type([timestamp/0]). +-export_type([time_unit/0]). +-export_type([deprecated_time_unit/0]). -type ext_binary() :: binary(). -type timestamp() :: {MegaSecs :: non_neg_integer(), Secs :: non_neg_integer(), MicroSecs :: non_neg_integer()}. +-type time_unit() :: + pos_integer() + | 'second' + | 'millisecond' + | 'microsecond' + | 'nanosecond' + | 'native' + | 'perf_counter' + | deprecated_time_unit(). + +%% Deprecated symbolic units... +-type deprecated_time_unit() :: + 'seconds' + | 'milli_seconds' + | '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) @@ -81,30 +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, 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/1]). +-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, halt/0, halt/1, halt/2, hash/2, hibernate/3]). +-export([group_leader/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([localtime/0, make_ref/0, map_size/1, match_spec_test/3, md5/1, md5_final/1]). +-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, 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]). @@ -112,13 +149,19 @@ -export([port_connect/2, port_control/3, port_get_data/1]). -export([port_set_data/2, port_to_list/1, ports/0]). -export([posixtime_to_universaltime/1, pre_loaded/0, prepare_loading/2]). +-export([monotonic_time/0, monotonic_time/1]). +-export([system_time/0, system_time/1]). +-export([convert_time_unit/3]). +-export([unique_integer/0, unique_integer/1]). +-export([time_offset/0, time_offset/1, timestamp/0]). -export([process_display/2]). -export([process_flag/3, process_info/1, processes/0, purge_module/1]). --export([put/2, raise/3, read_timer/1, ref_to_list/1, register/2]). --export([registered/0, resume_process/1, round/1, self/0, send_after/3]). +-export([put/2, raise/3, read_timer/1, read_timer/2, ref_to_list/1, register/2]). +-export([send_after/3, send_after/4, start_timer/3, start_timer/4]). +-export([registered/0, resume_process/1, round/1, self/0]). -export([seq_trace/2, seq_trace_print/1, seq_trace_print/2, setnode/2]). -export([setnode/3, size/1, spawn/3, spawn_link/3, split_binary/2]). --export([start_timer/3, suspend_process/2, system_monitor/0]). +-export([suspend_process/2, system_monitor/0]). -export([system_monitor/1, system_monitor/2, system_profile/0]). -export([system_profile/2, throw/1, time/0, trace/3, trace_delivered/1]). -export([trace_info/2, trunc/1, tuple_size/1, universaltime/0]). @@ -169,6 +212,8 @@ 'receive' | 'print' | 'timestamp' | + 'monotonic_timestamp' | + 'strict_monotonic_timestamp' | 'label' | 'serial'. @@ -182,7 +227,10 @@ 'exclusive' | 'runnable_ports' | 'runnable_procs' | - 'scheduler'. + 'scheduler' | + 'timestamp' | + 'monotonic_timestamp' | + 'strict_monotonic_timestamp'. -type system_monitor_option() :: 'busy_port' | @@ -206,25 +254,32 @@ send | 'receive' | procs | + ports | call | - silent | + arity | return_to | + silent | running | exiting | + running_procs | + running_ports | garbage_collection | timestamp | cpu_timestamp | - arity | + monotonic_timestamp | + strict_monotonic_timestamp | set_on_spawn | set_on_first_spawn | set_on_link | set_on_first_link | - {tracer, pid() | port()}. + {tracer, pid() | port()} | + {tracer, module(), term()}. -type trace_info_item_result() :: {traced, global | local | false | undefined} | {match_spec, trace_match_spec() | false | undefined} | {meta, pid() | port() | false | undefined | []} | + {meta, module(), term() } | {meta_match_spec, trace_match_spec() | false | undefined} | {call_count, non_neg_integer() | boolean() | undefined} | {call_time, [{pid(), non_neg_integer(), @@ -242,12 +297,15 @@ running | garbage_collection | timestamp | + monotonic_timestamp | + strict_monotonic_timestamp | arity. -type trace_info_return() :: undefined | {flags, [trace_info_flag()]} | {tracer, pid() | port() | []} | + {tracer, module(), term()} | trace_info_item_result() | {all, [ trace_info_item_result() ] | false | undefined}. @@ -365,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). @@ -410,12 +470,34 @@ call_on_load_function(_P1) -> erlang:nif_error(undefined). %% cancel_timer/1 --spec erlang:cancel_timer(TimerRef) -> Time | false when +-spec erlang:cancel_timer(TimerRef) -> Result when TimerRef :: reference(), - Time :: non_neg_integer(). + Time :: non_neg_integer(), + Result :: Time | false. + cancel_timer(_TimerRef) -> erlang:nif_error(undefined). +%% cancel_timer/2 +-spec erlang:cancel_timer(TimerRef, Options) -> Result | ok when + TimerRef :: reference(), + Async :: boolean(), + Info :: boolean(), + Option :: {async, Async} | {info, Info}, + Options :: [Option], + Time :: non_neg_integer(), + Result :: Time | false. + +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(). @@ -429,7 +511,7 @@ check_old_code(_Module) -> CheckResult :: boolean(). check_process_code(Pid, Module) -> try - erlang:check_process_code(Pid, Module, [{allow_gc, true}]) + erts_internal:check_process_code(Pid, Module, [{allow_gc, true}]) catch error:Error -> erlang:error(Error, [Pid, Module]) end. @@ -444,51 +526,11 @@ check_process_code(Pid, Module) -> CheckResult :: boolean() | aborted. check_process_code(Pid, Module, OptionList) -> try - {Async, AllowGC} = get_cpc_opts(OptionList, sync, true), - case Async of - {async, ReqId} -> - {priority, Prio} = erlang:process_info(erlang:self(), - priority), - erts_internal:request_system_task(Pid, - Prio, - {check_process_code, - ReqId, - Module, - AllowGC}), - async; - sync -> - case Pid == erlang:self() of - true -> - erts_internal:check_process_code(Module, - [{allow_gc, AllowGC}]); - false -> - {priority, Prio} = erlang:process_info(erlang:self(), - priority), - ReqId = erlang:make_ref(), - erts_internal:request_system_task(Pid, - Prio, - {check_process_code, - ReqId, - Module, - AllowGC}), - receive - {check_process_code, ReqId, CheckResult} -> - CheckResult - end - end - end + erts_internal:check_process_code(Pid, Module, OptionList) catch error:Error -> erlang:error(Error, [Pid, Module, OptionList]) end. -% gets async and allow_gc opts and verify valid option list -get_cpc_opts([{async, _ReqId} = AsyncTuple | Options], _OldAsync, AllowGC) -> - get_cpc_opts(Options, AsyncTuple, AllowGC); -get_cpc_opts([{allow_gc, AllowGC} | Options], Async, _OldAllowGC) -> - get_cpc_opts(Options, Async, AllowGC); -get_cpc_opts([], Async, AllowGC) -> - {Async, AllowGC}. - %% crc32/1 -spec erlang:crc32(Data) -> non_neg_integer() when Data :: iodata(). @@ -658,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(), @@ -751,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(). @@ -765,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) -> @@ -819,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(), @@ -853,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 @@ -866,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 @@ -907,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(). @@ -931,6 +984,12 @@ get() -> get(_Key) -> erlang:nif_error(undefined). +%% get_keys/0 +-spec get_keys() -> [Key] when + Key :: term(). +get_keys() -> + erlang:nif_error(undefined). + %% get_keys/1 -spec get_keys(Val) -> [Key] when Val :: term(), @@ -939,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 @@ -958,21 +1018,33 @@ 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 -spec halt() -> no_return(). halt() -> - erlang:nif_error(undefined). + erlang:halt(0, []). %% halt/1 %% Shadowed by erl_bif_types: erlang:halt/1 -spec halt(Status) -> no_return() when Status :: non_neg_integer() | 'abort' | string(). -halt(_Status) -> - erlang:nif_error(undefined). +halt(Status) -> + erlang:halt(Status, []). %% halt/2 %% Shadowed by erl_bif_types: erlang:halt/2 @@ -983,11 +1055,10 @@ 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) -> +%% has_prepared_code_on_load/1 +-spec erlang:has_prepared_code_on_load(PreparedCode) -> boolean() when + PreparedCode :: prepared_code(). +has_prepared_code_on_load(_PreparedCode) -> erlang:nif_error(undefined). %% hibernate/3 @@ -1031,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() -> @@ -1044,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(). @@ -1112,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()]. @@ -1141,11 +1237,19 @@ 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(P1, P2, P3) -> TestResult when - P1 :: [term()] | tuple(), - P2 :: term(), - P3 :: table | trace, +-spec erlang:match_spec_test(MatchAgainst, MatchSpec, Type) -> TestResult when + MatchAgainst :: [term()] | tuple(), + MatchSpec :: term(), + Type :: table | trace, TestResult :: {ok, term(), [return_trace], [ {error | warning, string()} ]} | {error, [ {error | warning, string()} ]}. match_spec_test(_P1, _P2, _P3) -> erlang:nif_error(undefined). @@ -1184,13 +1288,20 @@ md5_update(_Context, _Data) -> module_loaded(_Module) -> erlang:nif_error(undefined). +-type registered_name() :: atom(). +-type registered_process_identifier() :: registered_name() | {registered_name(), node()}. +-type monitor_process_identifier() :: pid() | registered_process_identifier(). +-type monitor_port_identifier() :: port() | registered_name(). + %% monitor/2 --spec monitor(Type, Item) -> MonitorRef when - Type :: process, - Item :: pid() | RegName | {RegName, Node}, - RegName :: module(), - Node :: node(), - MonitorRef :: reference(). +-spec monitor + (process, monitor_process_identifier()) -> MonitorRef + when MonitorRef :: reference(); + (port, monitor_port_identifier()) -> MonitorRef + when MonitorRef :: reference(); + (time_offset, clock_service) -> MonitorRef + when MonitorRef :: reference(). + monitor(_Type, _Item) -> erlang:nif_error(undefined). @@ -1276,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). @@ -1292,11 +1403,111 @@ ports() -> posixtime_to_universaltime(_P1) -> erlang:nif_error(undefined). +-spec erlang:unique_integer(ModifierList) -> integer() when + ModifierList :: [Modifier], + Modifier :: positive | monotonic. + +unique_integer(_ModifierList) -> + erlang:nif_error(undefined). + +-spec erlang:unique_integer() -> integer(). + +unique_integer() -> + erlang:nif_error(undefined). + +-spec erlang:monotonic_time() -> integer(). + +monotonic_time() -> + erlang:nif_error(undefined). + +-spec erlang:monotonic_time(Unit) -> integer() when + Unit :: time_unit(). + +monotonic_time(_Unit) -> + erlang:nif_error(undefined). + +-spec erlang:system_time() -> integer(). + +system_time() -> + erlang:nif_error(undefined). + +-spec erlang:system_time(Unit) -> integer() when + Unit :: time_unit(). + +system_time(_Unit) -> + erlang:nif_error(undefined). + +-spec erlang:convert_time_unit(Time, FromUnit, ToUnit) -> ConvertedTime when + Time :: integer(), + ConvertedTime :: integer(), + FromUnit :: time_unit(), + ToUnit :: time_unit(). + +convert_time_unit(Time, FromUnit, ToUnit) -> + try + FU = case FromUnit of + native -> erts_internal:time_unit(); + perf_counter -> erts_internal:perf_counter_unit(); + nanosecond -> 1000*1000*1000; + microsecond -> 1000*1000; + millisecond -> 1000; + second -> 1; + + %% Deprecated symbolic units... + nano_seconds -> 1000*1000*1000; + micro_seconds -> 1000*1000; + milli_seconds -> 1000; + seconds -> 1; + + _ when FromUnit > 0 -> FromUnit + end, + TU = case ToUnit of + native -> erts_internal:time_unit(); + perf_counter -> erts_internal:perf_counter_unit(); + nanosecond -> 1000*1000*1000; + microsecond -> 1000*1000; + millisecond -> 1000; + second -> 1; + + %% Deprecated symbolic units... + nano_seconds -> 1000*1000*1000; + micro_seconds -> 1000*1000; + milli_seconds -> 1000; + seconds -> 1; + + _ when ToUnit > 0 -> ToUnit + end, + case Time < 0 of + true -> TU*Time - (FU - 1); + false -> TU*Time + end div FU + catch + _ : _ -> + erlang:error(badarg, [Time, FromUnit, ToUnit]) + end. + +-spec erlang:time_offset() -> integer(). + +time_offset() -> + erlang:nif_error(undefined). + +-spec erlang:time_offset(Unit) -> integer() when + Unit :: time_unit(). + +time_offset(_Unit) -> + erlang:nif_error(undefined). + +-spec erlang:timestamp() -> Timestamp when + Timestamp :: timestamp(). + +timestamp() -> + erlang:nif_error(undefined). + %% prepare_loading/2 -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). @@ -1310,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 @@ -1319,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 @@ -1338,8 +1569,16 @@ processes() -> %% purge_module/1 -spec purge_module(Module) -> true when Module :: atom(). -purge_module(_Module) -> - erlang:nif_error(undefined). +purge_module(Module) when erlang:is_atom(Module) -> + case erts_code_purger:purge(Module) of + {false, _} -> + erlang:error(badarg, [Module]); + {true, _} -> + true + end; +purge_module(Arg) -> + erlang:error(badarg, [Arg]). + %% put/2 -spec put(Key, Val) -> term() when @@ -1357,13 +1596,28 @@ raise(_Class, _Reason, _Stacktrace) -> erlang:nif_error(undefined). %% read_timer/1 --spec erlang:read_timer(TimerRef) -> non_neg_integer() | false when - TimerRef :: reference(). +-spec erlang:read_timer(TimerRef) -> Result when + TimerRef :: reference(), + Time :: non_neg_integer(), + Result :: Time | false. + read_timer(_TimerRef) -> erlang:nif_error(undefined). +%% read_timer/2 +-spec erlang:read_timer(TimerRef, Options) -> Result | ok when + TimerRef :: reference(), + Async :: boolean(), + Option :: {async, Async}, + Options :: [Option], + Time :: non_neg_integer(), + Result :: Time | false. + +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). @@ -1406,9 +1660,23 @@ self() -> Dest :: pid() | atom(), Msg :: term(), TimerRef :: reference(). + send_after(_Time, _Dest, _Msg) -> erlang:nif_error(undefined). +%% send_after/4 +-spec erlang:send_after(Time, Dest, Msg, Options) -> TimerRef when + Time :: integer(), + Dest :: pid() | atom(), + Msg :: term(), + Options :: [Option], + Abs :: boolean(), + Option :: {abs, Abs}, + TimerRef :: reference(). + +send_after(_Time, _Dest, _Msg, _Options) -> + erlang:nif_error(undefined). + %% seq_trace/2 -spec erlang:seq_trace(P1, P2) -> seq_trace_info_returns() | {term(), term(), term(), term(), term()} when P1 :: atom(), @@ -1437,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 @@ -1480,16 +1762,53 @@ split_binary(_Bin, _Pos) -> Dest :: pid() | atom(), Msg :: term(), TimerRef :: reference(). + start_timer(_Time, _Dest, _Msg) -> erlang:nif_error(undefined). +%% start_timer/4 +-spec erlang:start_timer(Time, Dest, Msg, Options) -> TimerRef when + Time :: integer(), + Dest :: pid() | atom(), + Msg :: term(), + Options :: [Option], + Abs :: boolean(), + Option :: {abs, Abs}, + TimerRef :: reference(). + +start_timer(_Time, _Dest, _Msg, _Options) -> + erlang:nif_error(undefined). + %% suspend_process/2 -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 @@ -1548,12 +1867,35 @@ time() -> erlang:nif_error(undefined). %% trace/3 --spec erlang:trace(PidSpec, How, FlagList) -> integer() when - PidSpec :: pid() | existing | new | all, +-spec erlang:trace(PidPortSpec, How, FlagList) -> integer() when + PidPortSpec :: pid() | port() + | all | processes | ports + | existing | existing_processes | existing_ports + | new | new_processes | new_ports, How :: boolean(), FlagList :: [trace_flag()]. -trace(_PidSpec, _How, _FlagList) -> - erlang:nif_error(undefined). +trace(PidPortSpec, How, FlagList) -> + %% Make sure that we have loaded the tracer module + case lists:keyfind(tracer, 1, FlagList) of + {tracer, Module, State} when erlang:is_atom(Module) -> + case erlang:module_loaded(Module) of + false -> + Module:enabled(trace_status, erlang:self(), State); + true -> + ok + end; + _ -> + ignore + end, + + try erts_internal:trace(PidPortSpec, How, FlagList) of + Res -> Res + catch E:R -> + {_, [_ | CST]} = erlang:process_info( + erlang:self(), current_stacktrace), + erlang:raise( + E, R, [{?MODULE, trace, [PidPortSpec, How, FlagList], []} | CST]) + end. %% trace_delivered/1 -spec erlang:trace_delivered(Tracee) -> Ref when @@ -1563,14 +1905,16 @@ trace_delivered(_Tracee) -> erlang:nif_error(undefined). %% trace_info/2 --spec erlang:trace_info(PidOrFunc, Item) -> Res when - PidOrFunc :: pid() | new | {Module, Function, Arity} | on_load, +-spec erlang:trace_info(PidPortFuncEvent, Item) -> Res when + PidPortFuncEvent :: pid() | port() | new | new_processes | new_ports + | {Module, Function, Arity} | on_load | send | 'receive', Module :: module(), Function :: atom(), Arity :: arity(), - Item :: flags | tracer | traced | match_spec | meta | meta_match_spec | call_count | call_time | all, + Item :: flags | tracer | traced | match_spec + | meta | meta_match_spec | call_count | call_time | all, Res :: trace_info_return(). -trace_info(_PidOrFunc, _Item) -> +trace_info(_PidPortFuncEvent, _Item) -> erlang:nif_error(undefined). %% trunc/1 @@ -1649,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 | imports | exports | functions | attributes | compile | native_addresses, - ModuleInfo :: atom() | [] | [{atom(), arity()}] | [{atom(), term()}] | [{atom(), arity(), integer()}]. + Item :: module_info_key(), + ModuleInfo :: term(). get_module_info(_Module, _Item) -> erlang:nif_error(undefined). @@ -1778,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]} -> @@ -1805,6 +2151,7 @@ localtime_to_universaltime(_Localtime, _IsDst) -> %% CHECK! Why the strange very thorough specification of the error %% condition with disallowed arity in erl_bif_types? %% Not documented +%% Shadowed by erl_bif_types: erlang:make_fun/3 -spec erlang:make_fun(Module, Function, Arity) -> function() when Module :: atom(), Function :: atom(), @@ -1844,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 @@ -1857,12 +2204,21 @@ nodes(_Arg) -> | eof | {parallelism, Boolean :: boolean()} | hide. -open_port(_PortName,_PortSettings) -> - erlang:nif_error(undefined). +open_port(PortName, PortSettings) -> + case case erts_internal:open_port(PortName, PortSettings) of + Ref when erlang:is_reference(Ref) -> receive {Ref, Res} -> Res end; + Res -> Res + end of + Port when erlang:is_port(Port) -> Port; + Error -> erlang:error(Error, [PortName, PortSettings]) + end. -type priority_level() :: low | normal | high | max. +-type message_queue_data() :: + off_heap | on_heap. + -spec process_flag(trap_exit, Boolean) -> OldBoolean when Boolean :: boolean(), OldBoolean :: boolean(); @@ -1875,6 +2231,12 @@ open_port(_PortName,_PortSettings) -> (min_bin_vheap_size, MinBinVHeapSize) -> OldMinBinVHeapSize when MinBinVHeapSize :: non_neg_integer(), OldMinBinVHeapSize :: non_neg_integer(); + (max_heap_size, MaxHeapSize) -> OldMaxHeapSize when + MaxHeapSize :: max_heap_size(), + OldMaxHeapSize :: max_heap_size(); + (message_queue_data, MQD) -> OldMQD when + MQD :: message_queue_data(), + OldMQD :: message_queue_data(); (priority, Level) -> OldLevel when Level :: priority_level(), OldLevel :: priority_level(); @@ -1901,6 +2263,7 @@ process_flag(_Flag, _Value) -> dictionary | error_handler | garbage_collection | + garbage_collection_info | group_leader | heap_size | initial_call | @@ -1913,6 +2276,7 @@ process_flag(_Flag, _Value) -> min_bin_vheap_size | monitored_by | monitors | + message_queue_data | priority | reductions | registered_name | @@ -1940,6 +2304,7 @@ process_flag(_Flag, _Value) -> {dictionary, Dictionary :: [{Key :: term(), Value :: term()}]} | {error_handler, Module :: module()} | {garbage_collection, GCInfo :: [{atom(),non_neg_integer()}]} | + {garbage_collection_info, GCInfo :: [{atom(),non_neg_integer()}]} | {group_leader, GroupLeader :: pid()} | {heap_size, Size :: non_neg_integer()} | {initial_call, mfa()} | @@ -1950,13 +2315,15 @@ process_flag(_Flag, _Value) -> {messages, MessageQueue :: [term()]} | {min_heap_size, MinHeapSize :: non_neg_integer()} | {min_bin_vheap_size, MinBinVHeapSize :: non_neg_integer()} | - {monitored_by, Pids :: [pid()]} | + {max_heap_size, MaxHeapSize :: max_heap_size()} | + {monitored_by, MonitoredBy :: [pid() | port() | nif_resource()]} | {monitors, - Monitors :: [{process, Pid :: pid() | + Monitors :: [{process | port, Pid :: pid() | port() | {RegName :: atom(), Node :: node()}}]} | + {message_queue_data, MQD :: message_queue_data()} | {priority, Level :: priority_level()} | {reductions, Number :: non_neg_integer()} | - {registered_name, Atom :: atom()} | + {registered_name, [] | (Atom :: atom())} | {sequential_trace_token, [] | (SequentialTraceToken :: term())} | {stack_size, Size :: non_neg_integer()} | {status, Status :: exiting | garbage_collecting | waiting | running | runnable | suspended} | @@ -2008,6 +2375,8 @@ send(_Dest,_Msg,_Options) -> ('receive') -> {'receive', boolean()}; (print) -> {print, boolean()}; (timestamp) -> {timestamp, boolean()}; + (monotonic_timestamp) -> {timestamp, boolean()}; + (strict_monotonic_timestamp) -> {strict_monotonic_timestamp, boolean()}; (label) -> [] | {label, non_neg_integer()}; (serial) -> [] | {serial, {non_neg_integer(), non_neg_integer()}}. seq_trace_info(_What) -> @@ -2031,11 +2400,16 @@ setelement(_Index, _Tuple1, _Value) -> | {priority, Level :: priority_level()} | {fullsweep_after, Number :: non_neg_integer()} | {min_heap_size, Size :: non_neg_integer()} + | {max_heap_size, Size :: max_heap_size()} | {min_bin_vheap_size, VSize :: non_neg_integer()}. spawn_opt(_Tuple) -> erlang:nif_error(undefined). --spec statistics(context_switches) -> {ContextSwitches,0} when +-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, Exact_Reductions_Since_Last_Call} when @@ -2047,11 +2421,26 @@ spawn_opt(_Tuple) -> (io) -> {{input, Input}, {output, Output}} when Input :: non_neg_integer(), Output :: non_neg_integer(); + (microstate_accounting) -> [MSAcc_Thread] | undefined when + MSAcc_Thread :: #{ type := MSAcc_Thread_Type, + id := MSAcc_Thread_Id, + counters := MSAcc_Counters}, + 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 | + emulator | ets | gc | gc_fullsweep | nif | + other | port | send | sleep | timers; (reductions) -> {Total_Reductions, Reductions_Since_Last_Call} when Total_Reductions :: non_neg_integer(), Reductions_Since_Last_Call :: non_neg_integer(); (run_queue) -> 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(); @@ -2059,6 +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) -> 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(), @@ -2092,9 +2493,16 @@ 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(); + (microstate_accounting, Action) -> OldState when + Action :: true | false | reset, + OldState :: true | false; (min_heap_size, MinHeapSize) -> OldMinHeapSize when MinHeapSize :: non_neg_integer(), OldMinHeapSize :: non_neg_integer(); @@ -2102,9 +2510,12 @@ subtract(_,_) -> OldMinBinVHeapSize when MinBinVHeapSize :: non_neg_integer(), OldMinBinVHeapSize :: non_neg_integer(); + (max_heap_size, MaxHeapSize) -> OldMaxHeapSize when + MaxHeapSize :: max_heap_size(), + OldMaxHeapSize :: max_heap_size(); (multi_scheduling, BlockState) -> OldBlockState when - BlockState :: block | unblock, - OldBlockState :: block | unblock | enabled; + BlockState :: block | unblock | block_normal | unblock_normal, + OldBlockState :: blocked | disabled | enabled; (scheduler_bind_type, How) -> OldBindType when How :: scheduler_bind_type() | default_bind, OldBindType :: scheduler_bind_type(); @@ -2118,9 +2529,11 @@ subtract(_,_) -> (trace_control_word, TCW) -> OldTCW when TCW :: non_neg_integer(), OldTCW :: non_neg_integer(); + (time_offset, finalize) -> OldState when + OldState :: preliminary | final | volatile; %% These are deliberately not documented (internal_cpu_topology, term()) -> term(); - (sequential_tracer, pid() | port() | false) -> pid() | port() | false; + (sequential_tracer, pid() | port() | {module(), term()} | false) -> pid() | port() | false; (1,0) -> true. system_flag(_Flag, _Value) -> @@ -2135,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). @@ -2145,35 +2558,70 @@ 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(), + MFA :: trace_pattern_mfa() | send | 'receive', MatchSpec :: (MatchSpecList :: trace_match_spec()) | boolean() | restart | pause. -trace_pattern(_MFA, _MatchSpec) -> - erlang:nif_error(undefined). +trace_pattern(MFA, MatchSpec) -> + try erts_internal:trace_pattern(MFA, MatchSpec, []) of + Res -> Res + catch E:R -> + {_, [_ | CST]} = erlang:process_info( + erlang:self(), current_stacktrace), + erlang:raise( + E, R, [{?MODULE, trace_pattern, [MFA, MatchSpec], []} | CST]) + end. -type trace_pattern_flag() :: global | local | meta | {meta, Pid :: pid()} | + {meta, TracerModule :: module(), TracerState :: term()} | call_count | call_time. --spec erlang:trace_pattern(MFA, MatchSpec, FlagList) -> non_neg_integer() when +-spec erlang:trace_pattern(send, MatchSpec, []) -> non_neg_integer() when + MatchSpec :: (MatchSpecList :: trace_match_spec()) + | boolean(); + ('receive', MatchSpec, []) -> non_neg_integer() when + MatchSpec :: (MatchSpecList :: trace_match_spec()) + | boolean(); + (MFA, MatchSpec, FlagList) -> non_neg_integer() when MFA :: trace_pattern_mfa(), MatchSpec :: (MatchSpecList :: trace_match_spec()) | boolean() | restart | pause, FlagList :: [ trace_pattern_flag() ]. -trace_pattern(_MFA, _MatchSpec, _FlagList) -> - erlang:nif_error(undefined). +trace_pattern(MFA, MatchSpec, FlagList) -> + %% Make sure that we have loaded the tracer module + case lists:keyfind(meta, 1, FlagList) of + {meta, Module, State} when erlang:is_atom(Module) -> + case erlang:module_loaded(Module) of + false -> + Module:enabled(trace_status, erlang:self(), State); + true -> + ok + end; + _ -> + ignore + end, + + try erts_internal:trace_pattern(MFA, MatchSpec, FlagList) of + Res -> Res + catch E:R -> + {_, [_ | CST]} = erlang:process_info( + erlang:self(), current_stacktrace), + erlang:raise( + E, R, [{?MODULE, trace_pattern, [MFA, MatchSpec, FlagList], []} | CST]) + end. %% Shadowed by erl_bif_types: erlang:tuple_to_list/1 -spec tuple_to_list(Tuple) -> [term()] when @@ -2205,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()}; @@ -2222,6 +2672,7 @@ tuple_to_list(_Tuple) -> CpuTopology :: cpu_topology(); (creation) -> integer(); (debug_compiled) -> boolean(); + (delayed_node_table_gc) -> infinity | non_neg_integer(); (dirty_cpu_schedulers) -> non_neg_integer(); (dirty_cpu_schedulers_online) -> non_neg_integer(); (dirty_io_schedulers) -> non_neg_integer(); @@ -2230,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()}]; @@ -2246,14 +2699,20 @@ tuple_to_list(_Tuple) -> logical_processors_available | logical_processors_online) -> unknown | pos_integer(); (machine) -> string(); + (max_heap_size) -> {max_heap_size, MaxHeapSize :: max_heap_size()}; + (message_queue_data) -> message_queue_data(); (min_heap_size) -> {min_heap_size, MinHeapSize :: pos_integer()}; (min_bin_vheap_size) -> {min_bin_vheap_size, MinBinVHeapSize :: pos_integer()}; (modified_timing_level) -> integer() | undefined; - (multi_scheduling) -> disabled | blocked | enabled; - (multi_scheduling_blockers) -> [PID :: pid()]; + (multi_scheduling) -> disabled | blocked | blocked_normal | enabled; + (multi_scheduling_blockers) -> [Pid :: pid()]; (nif_version) -> string(); + (normal_multi_scheduling_blockers) -> [Pid :: pid()]; (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(); @@ -2271,15 +2730,20 @@ tuple_to_list(_Tuple) -> (scheduler_id) -> SchedulerId :: pos_integer(); (schedulers | schedulers_online) -> pos_integer(); (smp_support) -> boolean(); + (start_time) -> integer(); (system_version) -> string(); (system_architecture) -> string(); (threads) -> boolean(); (thread_pool_size) -> non_neg_integer(); + (time_correction) -> true | false; + (time_offset) -> preliminary | final | volatile; + (time_warp_mode) -> no_time_warp | single_time_warp | multi_time_warp; (tolerant_timeofday) -> enabled | disabled; (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). @@ -2296,6 +2760,7 @@ universaltime_to_localtime(_Universaltime) -> %%-------------------------------------------------------------------------- +%% Shadowed by erl_bif_types: erlang:apply/2 -spec apply(Fun, Args) -> term() when Fun :: function(), Args :: [term()]. @@ -2374,14 +2839,27 @@ spawn_monitor(M, F, A) when erlang:is_atom(M), spawn_monitor(M, F, A) -> erlang:error(badarg, [M,F,A]). + +-type max_heap_size() :: + Size :: non_neg_integer() + %% TODO change size => to := when -type maps support is finalized + | #{ size => non_neg_integer(), + kill => boolean(), + error_logger => boolean() }. + +-type spawn_opt_option() :: + link + | monitor + | {priority, Level :: priority_level()} + | {fullsweep_after, Number :: non_neg_integer()} + | {min_heap_size, Size :: non_neg_integer()} + | {min_bin_vheap_size, VSize :: non_neg_integer()} + | {max_heap_size, Size :: max_heap_size()} + | {message_queue_data, MQD :: message_queue_data()}. + -spec spawn_opt(Fun, Options) -> pid() | {pid(), reference()} when Fun :: function(), - Options :: [Option], - Option :: link | monitor - | {priority, Level :: priority_level()} - | {fullsweep_after, Number :: non_neg_integer()} - | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}. + Options :: [spawn_opt_option()]. spawn_opt(F, O) when erlang:is_function(F) -> spawn_opt(erlang, apply, [F, []], O); spawn_opt({M,F}=MF, O) when erlang:is_atom(M), erlang:is_atom(F) -> @@ -2394,12 +2872,7 @@ spawn_opt(F, O) -> -spec spawn_opt(Node, Fun, Options) -> pid() | {pid(), reference()} when Node :: node(), Fun :: function(), - Options :: [Option], - Option :: link | monitor - | {priority, Level :: priority_level()} - | {fullsweep_after, Number :: non_neg_integer()} - | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}. + Options :: [spawn_opt_option()]. spawn_opt(N, F, O) when N =:= erlang:node() -> spawn_opt(F, O); spawn_opt(N, F, O) when erlang:is_function(F) -> @@ -2486,12 +2959,7 @@ spawn_link(N,M,F,A) -> Module :: module(), Function :: atom(), Args :: [term()], - Options :: [Option], - Option :: link | monitor - | {priority, Level :: priority_level()} - | {fullsweep_after, Number :: non_neg_integer()} - | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}. + Options :: [spawn_opt_option()]. spawn_opt(M, F, A, Opts) -> case catch erlang:spawn_opt({M,F,A,Opts}) of {'EXIT',{Reason,_}} -> @@ -2506,12 +2974,7 @@ spawn_opt(M, F, A, Opts) -> Module :: module(), Function :: atom(), Args :: [term()], - Options :: [Option], - Option :: link | monitor - | {priority, Level :: priority_level()} - | {fullsweep_after, Number :: non_neg_integer()} - | {min_heap_size, Size :: non_neg_integer()} - | {min_bin_vheap_size, VSize :: non_neg_integer()}. + Options :: [spawn_opt_option()]. spawn_opt(N, M, F, A, O) when N =:= erlang:node(), erlang:is_atom(M), erlang:is_atom(F), erlang:is_list(A), erlang:is_list(O) -> @@ -2640,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 %% @@ -2806,6 +3260,9 @@ port_info(Port) -> (Port, monitors) -> {monitors, Monitors} | 'undefined' when Port :: port() | atom(), Monitors :: [{process, pid()}]; + (Port, monitored_by) -> {monitored_by, MonitoredBy} | 'undefined' when + Port :: port() | atom(), + MonitoredBy :: [pid()]; (Port, name) -> {name, Name} | 'undefined' when Port :: port() | atom(), Name :: string(); @@ -2848,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 -> @@ -2886,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. %% @@ -3041,16 +3450,6 @@ integer_to_binary(I0, Base, R0) -> true -> integer_to_binary(I1, Base, R1) end. -%% erlang:flush_monitor_message/2 is for internal use only! -%% -%% erlang:demonitor(Ref, [flush]) traps to -%% erlang:flush_monitor_message(Ref, Res) when -%% it needs to flush a monitor message. -flush_monitor_message(Ref, Res) when erlang:is_reference(Ref), - erlang:is_atom(Res) -> - receive {_, Ref, _, _, _} -> ok after 0 -> ok end, - Res. - -record(cpu, {node = -1, processor = -1, processor_node = -1, @@ -3194,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(), @@ -3244,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)). @@ -3260,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(), @@ -3272,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}, @@ -3288,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(); @@ -3376,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}. @@ -3398,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() -> @@ -3413,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 -> @@ -3432,9 +3779,13 @@ blocks_size([], Acc) -> Acc. get_fix_proc([{ProcType, A1, U1}| Rest], {A0, U0}) when ProcType == proc; - ProcType == monitor_sh; - ProcType == nlink_sh; - ProcType == msg_ref -> + ProcType == monitor; + ProcType == link; + ProcType == msg_ref; + ProcType == ll_ptimer; + ProcType == hl_ptimer; + ProcType == bif_timer; + ProcType == accessor_bif_timer -> get_fix_proc(Rest, {A0+A1, U0+U1}); get_fix_proc([_|Rest], Acc) -> get_fix_proc(Rest, Acc); @@ -3451,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} | _]) -> @@ -3513,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. @@ -3544,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 @@ -3572,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, @@ -3657,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(). @@ -3701,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 345a6ae3be..8c34c99a98 100644 --- a/erts/preloaded/src/erts.app.src +++ b/erts/preloaded/src/erts.app.src @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -26,6 +27,8 @@ erts_internal, init, otp_ring0, + erts_code_purger, + prim_buffer, prim_eval, prim_file, prim_inet, @@ -35,7 +38,7 @@ {registered, []}, {applications, []}, {env, []}, - {runtime_dependencies, ["stdlib-2.0", "kernel-3.0", "sasl-2.4"]} + {runtime_dependencies, ["stdlib-3.5", "kernel-6.1", "sasl-3.0.1"]} ]}. %% vim: ft=erlang diff --git a/erts/preloaded/src/erts_code_purger.erl b/erts/preloaded/src/erts_code_purger.erl new file mode 100644 index 0000000000..c41532ed87 --- /dev/null +++ b/erts/preloaded/src/erts_code_purger.erl @@ -0,0 +1,377 @@ +%% +%% %CopyrightBegin% +%% +%% 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. +%% 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_code_purger). + +%% Purpose : Implement system process erts_code_purger +%% to handle code module purging. + +-export([start/0, purge/1, soft_purge/1, pending_purge_lambda/3, + finish_after_on_load/2]). + +-spec start() -> no_return(). +start() -> + register(erts_code_purger, self()), + process_flag(trap_exit, true), + 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 +%% a module that currently is being purged will end +%% up here (pending_purge_lambda) in a suspended state. +%% When the purge operation completes or aborts (soft +%% purge that failed) these processes will be resumed. +%% +pending_purge_lambda(_Module, Fun, Args) -> + %% + %% When the process is resumed, the following + %% scenarios exist: + %% * The code that the fun refers to is still + %% there due to a failed soft purge. The + %% call to the fun will succeed via apply/2. + %% * The code was purged, and a current version + %% of the module is loaded which does not + %% contain this fun. The call will result + %% in an exception being raised. + %% * The code was purged, and no current + %% version of the module is loaded. An attempt + %% to load the module (via the error_handler) + %% will be made. This may or may not succeed. + %% If the module is loaded, it may or may + %% not contain the fun. The call will + %% succeed if the error_handler was able + %% to load the module and loaded module + %% contains this fun; otherwise, an exception + %% will be raised. + %% + apply(Fun, Args). + +%% purge(Module) +%% Kill all processes running code from *old* Module, and then purge the +%% module. Return {WasOld, DidKill}: +%% {false, false} there was no old module to purge +%% {true, false} module purged, no process killed +%% {true, true} module purged, at least one process killed + +purge(Mod) when is_atom(Mod) -> + Ref = make_ref(), + erts_code_purger ! {purge, Mod, self(), Ref}, + receive + {reply, purge, Result, Ref} -> + Result + end. + +do_purge(Mod, Reqs) -> + case erts_internal:purge_module(Mod, prepare) of + false -> + {{false, false}, Reqs}; + true -> + {DidKill, NewReqs} = check_proc_code(erlang:processes(), + Mod, true, Reqs), + true = erts_internal:purge_module(Mod, complete), + {{true, DidKill}, NewReqs} + end. + +%% soft_purge(Module) +%% Purge old code only if no procs remain that run old code. +%% Return true in that case, false if procs remain (in this +%% case old code is not purged) + +soft_purge(Mod) -> + Ref = make_ref(), + erts_code_purger ! {soft_purge, Mod, self(), Ref}, + receive + {reply, soft_purge, Result, Ref} -> + Result + end. + +do_soft_purge(Mod, Reqs) -> + case erts_internal:purge_module(Mod, prepare) of + false -> + {true, Reqs}; + true -> + {PurgeOp, NewReqs} = check_proc_code(erlang:processes(), + Mod, false, Reqs), + {erts_internal:purge_module(Mod, PurgeOp), NewReqs} + end. + +%% finish_after_on_load(Module, Keep) +%% Finish after running on_load function. If Keep is false, +%% purge the code for the on_load function. + +finish_after_on_load(Mod, Keep) -> + Ref = make_ref(), + erts_code_purger ! {finish_after_on_load, {Mod,Keep}, self(), Ref}, + receive + {reply, finish_after_on_load, Result, Ref} -> + Result + end. + +do_finish_after_on_load(Mod, Keep, Reqs) -> + erlang:finish_after_on_load(Mod, Keep), + case Keep of + true -> + Reqs; + false -> + case erts_internal:purge_module(Mod, prepare_on_load) of + false -> + Reqs; + true -> + {_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, 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', 'abort' will +%% be returned; otherwise, 'complete'. +%% +%% 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. +%% + +-define(MAX_CPC_NO_OUTSTANDING_KILLS, 10). + +-record(cpc_static, {hard, module, tag, purge_requests}). + +-record(cpc_kill, {outstanding = [], + no_outstanding = 0, + waiting = [], + killed = false}). + +check_proc_code(Pids, Mod, Hard, PReqs) -> + Tag = erlang:make_ref(), + CpcS = #cpc_static{hard = Hard, + module = Mod, + 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... + 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; 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... + KillState1 = cpc_sched_kill(Pid, KillState0), + cpc_receive(CpcS, NoReq-1, KillState1, PReqs) + end; + {'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_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_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_sched_kill_waiting(#cpc_kill{waiting = []} = KillState) -> + KillState; +cpc_sched_kill_waiting(#cpc_kill{outstanding = Rs, + no_outstanding = N, + waiting = [P|Ps]} = KillState) -> + R = erlang:monitor(process, P), + exit(P, kill), + KillState#cpc_kill{outstanding = [R|Rs], + no_outstanding = N+1, + waiting = Ps, + killed = true}. + +cpc_sched_kill(Pid, #cpc_kill{no_outstanding = N, waiting = Pids} = KillState) + when N >= ?MAX_CPC_NO_OUTSTANDING_KILLS -> + KillState#cpc_kill{waiting = [Pid|Pids]}; +cpc_sched_kill(Pid, + #cpc_kill{outstanding = Rs, no_outstanding = N} = KillState) -> + R = erlang:monitor(process, Pid), + exit(Pid, kill), + KillState#cpc_kill{outstanding = [R|Rs], + no_outstanding = N+1, + killed = true}. + +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), + cpc_init(CpcS, Pids, NoReqs+1). + +% end of check_proc_code() implementation. + +%% +%% FOR TESTING ONLY +%% +%% do_test_purge() is for testing only. The purge is done +%% as usual, but the tester can control when to enter the +%% specific phases. +%% +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}, + NewReqs; +do_test_purge(_, _, _, _, Reqs) -> + Reqs. + +do_test_soft_purge(Mod, From, Ref, Reqs) -> + PrepRes = erts_internal:purge_module(Mod, prepare), + TestRes = test_progress(started, From, Ref, ok), + case PrepRes of + false -> + _ = test_progress(continued, From, Ref, TestRes), + {true, Reqs}; + true -> + {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, Reqs) -> + PrepRes = erts_internal:purge_module(Mod, prepare), + TestRes = test_progress(started, From, Ref, ok), + case PrepRes of + false -> + _ = test_progress(continued, From, Ref, TestRes), + {{false, false}, Reqs}; + true -> + {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}, NewReqs} + end. + +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, Ref, ok) -> + From ! {started, Ref}, + Mon = erlang:monitor(process, From), + receive + {'DOWN', Mon, process, From, _} -> died; + {continue, Ref} -> erlang:demonitor(Mon, [flush]), ok + end; +test_progress(continued, From, Ref, ok) -> + From ! {continued, Ref}, + Mon = erlang:monitor(process, From), + receive + {'DOWN', Mon, process, From, _} -> died; + {complete, Ref} -> erlang:demonitor(Mon, [flush]), ok + end. + 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 2c5bd82cf0..88f47e917b 100644 --- a/erts/preloaded/src/erts_internal.erl +++ b/erts/preloaded/src/erts_internal.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012-2013. All Rights Reserved. +%% Copyright Ericsson AB 2012-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -30,13 +31,64 @@ -export([await_port_send_result/3]). -export([cmp_term/2]). --export([map_to_tuple_keys/1]). --export([port_command/3, port_connect/2, port_close/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]). --export([request_system_task/3]). +-export([system_check/1, + 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]). + +-export([flush_monitor_messages/3]). + +-export([await_result/1, gather_io_bytes/2]). + +-export([time_unit/0, perf_counter_unit/0]). + +-export([is_system_process/1]). + +-export([await_microstate_accounting_modifications/3, + gather_microstate_accounting_result/2]). + +-export([trace/3, trace_pattern/3]). + +-export([dist_ctrl_put_data/2]). --export([check_process_code/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/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]). %% %% Await result of send to port @@ -49,9 +101,43 @@ await_port_send_result(Ref, Busy, Ok) -> end. %% +%% Await result... +%% + +await_result(Ref) when is_reference(Ref) -> + receive + {Ref, Result} -> + Result + end. + +%% +%% statistics(io) end up in gather_io_bytes/2 +%% + +gather_io_bytes(Ref, No) when is_reference(Ref), + is_integer(No), + No > 0 -> + gather_io_bytes(Ref, No, 0, 0). + +gather_io_bytes(_Ref, 0, InAcc, OutAcc) -> + {{input, InAcc}, {output, OutAcc}}; +gather_io_bytes(Ref, No, InAcc, OutAcc) -> + receive + {Ref, _SchedId, In, Out} -> + gather_io_bytes(Ref, No-1, InAcc + In, OutAcc + Out) + end. + +%% %% Statically linked port NIFs %% +-spec erts_internal:open_port(PortName, PortSettings) -> Result when + PortName :: tuple(), + PortSettings :: term(), + Result :: port() | reference() | atom(). +open_port(_PortName, _PortSettings) -> + erlang:nif_error(undefined). + -spec erts_internal:port_command(Port, Data, OptionList) -> Result when Port :: port() | atom(), Data :: iodata(), @@ -147,20 +233,131 @@ 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(), boolean()}, + Type :: 'major' | 'minor', + Request :: {'garbage_collect', term(), Type} + | {'check_process_code', term(), module()} + | {'copy_literals', term(), boolean()}, Pid :: pid(). request_system_task(_Pid, _Prio, _Request) -> erlang:nif_error(undefined). --spec check_process_code(Module, OptionList) -> boolean() when +-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()} + | {'copy_literals', term(), boolean()}, + RequesterPid :: pid(), + TargetPid :: pid(). + +request_system_task(_RequesterPid, _TargetPid, _Prio, _Request) -> + erlang:nif_error(undefined). + +-spec garbage_collect(Mode) -> 'true' when Mode :: 'major' | 'minor'. + +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 + Pid :: pid(), Module :: module(), - Option :: {allow_gc, boolean()}, - OptionList :: [Option]. -check_process_code(_Module, _OptionList) -> + RequestId :: term(), + Option :: {async, RequestId} | {allow_gc, boolean()}, + OptionList :: [Option], + CheckResult :: boolean() | aborted. +check_process_code(Pid, Module, OptionList) -> + Async = get_cpc_opts(OptionList, sync), + case Async of + {async, ReqId} -> + {priority, Prio} = erlang:process_info(erlang:self(), + priority), + erts_internal:request_system_task(Pid, + Prio, + {check_process_code, + ReqId, + Module}), + async; + sync -> + case Pid == erlang:self() of + true -> + erts_internal:check_process_code(Module); + false -> + {priority, Prio} = erlang:process_info(erlang:self(), + priority), + ReqId = erlang:make_ref(), + erts_internal:request_system_task(Pid, + Prio, + {check_process_code, + ReqId, + Module}), + receive + {check_process_code, ReqId, CheckResult} -> + CheckResult + end + end + end. + +% 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) -> + erlang:nif_error(undefined). + +-spec is_process_executing_dirty(Pid) -> 'true' | 'false' when + Pid :: pid(). +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() -> erlang:nif_error(undefined). +-spec purge_module(Module, Op) -> boolean() when + Module :: module(), + Op :: 'prepare' | 'prepare_on_load' | 'abort' | 'complete'. +purge_module(_Module, _Op) -> + erlang:nif_error(undefined). + +-spec system_check(Type) -> 'ok' when + Type :: 'schedulers'. + +system_check(_Type) -> + erlang:nif_error(undefined). + +gather_system_check_result(Ref) when is_reference(Ref) -> + gather_system_check_result(Ref, erlang:system_info(schedulers)). + +gather_system_check_result(_Ref, 0) -> + ok; +gather_system_check_result(Ref, N) -> + receive + Ref -> + gather_system_check_result(Ref, N - 1) + end. + %% term compare where integer() < float() = true -spec cmp_term(A,B) -> Result when @@ -178,3 +375,319 @@ cmp_term(_A,_B) -> map_to_tuple_keys(_M) -> erlang:nif_error(undefined). + +%% return the internal term type +-spec term_type(T) -> Type when + T :: term(), + Type :: 'flatmap' | 'hashmap' | 'hashmap_node' + | 'fixnum' | 'bignum' | 'hfloat' + | 'list' | 'tuple' | 'export' | 'fun' + | 'refc_binary' | 'heap_binary' | 'sub_binary' + | 'reference' | 'external_reference' + | 'pid' | 'external_pid' | 'port' | 'external_port' + | 'atom' | 'catch' | 'nil'. + +term_type(_T) -> + erlang:nif_error(undefined). + +%% return the internal hashmap sub-nodes from +%% a hashmap node +-spec map_hashmap_children(M) -> Children when + M :: map(), %% hashmap node + Children :: [map() | nonempty_improper_list(term(),term())]. + +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(), + Res :: term(). + +%% erlang:demonitor(Ref, [flush]) traps to +%% erts_internal:flush_monitor_messages(Ref, Res) when +%% it needs to flush monitor messages. +flush_monitor_messages(Ref, Multi, Res) when is_reference(Ref) -> + receive + {_, Ref, _, _, _} -> + case Multi of + false -> + Res; + _ -> + flush_monitor_messages(Ref, Multi, Res) + end + after 0 -> + Res + end. + +-spec erts_internal:time_unit() -> pos_integer(). + +time_unit() -> + erlang:nif_error(undefined). + +-spec erts_internal:perf_counter_unit() -> pos_integer(). + +perf_counter_unit() -> + erlang:nif_error(undefined). + +-spec erts_internal:is_system_process(Pid) -> boolean() when + Pid :: pid(). + +is_system_process(_Pid) -> + erlang:nif_error(undefined). + +-spec await_microstate_accounting_modifications(Ref, Result, Threads) -> boolean() when + Ref :: reference(), + Result :: boolean(), + Threads :: pos_integer(). + +await_microstate_accounting_modifications(Ref, Result, Threads) -> + _ = microstate_accounting(Ref,Threads), + Result. + +-spec gather_microstate_accounting_result(Ref, Threads) -> [#{}] when + Ref :: reference(), + Threads :: pos_integer(). + +gather_microstate_accounting_result(Ref, Threads) -> + microstate_accounting(Ref, Threads). + +microstate_accounting(_Ref, 0) -> + []; +microstate_accounting(Ref, Threads) -> + receive + Ref -> microstate_accounting(Ref, Threads - 1); + {Ref, Res} -> + [Res | microstate_accounting(Ref, Threads - 1)] + end. + +-spec trace(PidPortSpec, How, FlagList) -> integer() when + PidPortSpec :: pid() | port() + | all | processes | ports + | existing | existing_processes | existing_ports + | new | new_processes | new_ports, + How :: boolean(), + FlagList :: []. +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()] | '_' | match_variable() ,[term()],[term()]}]. + +-spec trace_pattern(MFA, MatchSpec, FlagList) -> non_neg_integer() when + MFA :: trace_pattern_mfa(), + MatchSpec :: (MatchSpecList :: trace_match_spec()) + | boolean() + | restart + | pause, + 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). diff --git a/erts/preloaded/src/erts_literal_area_collector.erl b/erts/preloaded/src/erts_literal_area_collector.erl new file mode 100644 index 0000000000..3befad8dfb --- /dev/null +++ b/erts/preloaded/src/erts_literal_area_collector.erl @@ -0,0 +1,113 @@ +%% +%% %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_literal_area_collector). + +-export([start/0]). + +%% Currently we only allow two outstanding literal +%% copying jobs that garbage collect in order to +%% copy the literals. Maybe we could allow more +%% than two outstanding processes, but for now we +%% play it safe... +-define(MAX_GC_OUTSTND, 2). + +%% +%% The erts_literal_area_collector 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(undefined, 0, 0, []). + +%% +%% The VM will send us a 'copy_literals' message +%% when it has a new literal area that needs to +%% be handled is added. We will also be informed +%% about more areas when we call +%% erts_internal:release_literal_area_switch(). +%% +msg_loop(Area, Outstnd, GcOutstnd, NeedGC) -> + receive + + %% A new area to handle has arrived... + copy_literals when Outstnd == 0 -> + switch_area(); + + %% Process (_Pid) has completed the request... + {copy_literals, {Area, _GcAllowed, _Pid}, ok} when Outstnd == 1 -> + switch_area(); %% Last process completed... + {copy_literals, {Area, false, _Pid}, ok} -> + msg_loop(Area, Outstnd-1, GcOutstnd, NeedGC); + {copy_literals, {Area, true, _Pid}, ok} when NeedGC == [] -> + msg_loop(Area, Outstnd-1, GcOutstnd-1, []); + {copy_literals, {Area, true, _Pid}, ok} -> + send_copy_req(hd(NeedGC), Area, true), + msg_loop(Area, Outstnd-1, GcOutstnd, tl(NeedGC)); + + %% Process (Pid) failed to complete the request + %% since it needs to garbage collect in order to + %% complete the request... + {copy_literals, {Area, false, Pid}, need_gc} when GcOutstnd < ?MAX_GC_OUTSTND -> + send_copy_req(Pid, Area, true), + msg_loop(Area, Outstnd, GcOutstnd+1, NeedGC); + {copy_literals, {Area, false, Pid}, need_gc} -> + msg_loop(Area, Outstnd, GcOutstnd, [Pid|NeedGC]); + + %% Not handled message regarding the area that we + %% currently are working with. Crash the VM so + %% we notice this bug... + {copy_literals, {Area, _, _}, _} = Msg when erlang:is_reference(Area) -> + exit({not_handled_message, Msg}); + + %% Unexpected garbage message. Get rid of it... + _Ignore -> + msg_loop(Area, Outstnd, GcOutstnd, NeedGC) + + end. + +switch_area() -> + Res = erts_internal:release_literal_area_switch(), + erlang:garbage_collect(), %% Almost no live data now... + case Res of + false -> + %% No more areas to handle... + msg_loop(undefined, 0, 0, []); + true -> + %% Send requests to all processes to copy + %% all live data they have referring to the + %% literal area that is to be released... + Area = make_ref(), + Outstnd = send_copy_reqs(erlang:processes(), Area, false), + msg_loop(Area, Outstnd, 0, []) + end. + +send_copy_reqs(Ps, Area, GC) -> + send_copy_reqs(Ps, Area, GC, 0). + +send_copy_reqs([], _Area, _GC, N) -> + N; +send_copy_reqs([P|Ps], Area, GC, N) -> + send_copy_req(P, Area, GC), + send_copy_reqs(Ps, Area, GC, N+1). + +send_copy_req(P, Area, GC) -> + erts_internal:request_system_task(P, normal, {copy_literals, {Area, GC, P}, GC}). diff --git a/erts/preloaded/src/init.erl b/erts/preloaded/src/init.erl index e95e11b3e6..253fcf7a1f 100644 --- a/erts/preloaded/src/init.erl +++ b/erts/preloaded/src/init.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2013. All Rights Reserved. +%% Copyright Ericsson AB 1996-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -22,7 +23,6 @@ %% a local file or distributed from another erlang node. %% %% Flags: -%% -id Identity : identity of the system. %% -boot File : Absolute file name of the boot script. %% -boot_var Var Value %% : $Var in the boot script is expanded to @@ -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. @@ -41,6 +41,7 @@ %% -s : Start own processes. %% %% Experimental flags: +%% -profile_boot : Use an 'eprof light' to profile boot sequence %% -init_debug : Activate debug printouts in init %% -loader_debug : Activate debug printouts in erl_prim_loader %% -code_path_choice : strict | relaxed @@ -74,8 +75,22 @@ subscribed = []}). -type state() :: #state{}. +%% Data for eval_script/2. +-record(es, + {init, + debug, + path, + pa, + pz, + path_choice, + prim_load, + load_mode, + vars + }). + -define(ON_LOAD_HANDLER, init__boot__on_load_handler). + debug(false, _) -> ok; debug(_, T) -> erlang:display(T). @@ -116,7 +131,7 @@ bs2ss(L) -> get_status() -> request(get_status). --spec fetch_loaded() -> [atom()]. +-spec fetch_loaded() -> [{module(),file:filename()}]. fetch_loaded() -> request(fetch_loaded). @@ -159,18 +174,46 @@ stop() -> init ! {stop,stop}, ok. -spec stop(Status) -> 'ok' when Status :: non_neg_integer() | string(). -stop(Status) -> init ! {stop,{stop,Status}}, ok. +stop(Status) when is_integer(Status), Status >= 0 -> + stop_1(Status); +stop(Status) when is_list(Status) -> + case is_bytelist(Status) of + true -> + stop_1(Status); + false -> + erlang:error(badarg) + end; +stop(_) -> + erlang:error(badarg). + +is_bytelist([B|Bs]) when is_integer(B), B >= 0, B < 256 -> is_bytelist(Bs); +is_bytelist([]) -> true; +is_bytelist(_) -> false. + +%% Note that we check the type of Status beforehand to ensure that +%% the call to halt(Status) by the init process cannot fail +stop_1(Status) -> init ! {stop,{stop,Status}}, ok. -spec boot(BootArgs) -> no_return() when BootArgs :: [binary()]. boot(BootArgs) -> register(init, self()), process_flag(trap_exit, true), - start_on_load_handler_process(), + + %% 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 b2a(get_flag(profile_boot, Flags, false)) of + false -> ok; + true -> debug_profile_start() + end, Start = map(fun prepare_run_args/1, Start0), - Flags0 = flags_to_atoms_again(Flags), - boot(Start,Flags0,Args). + boot(Start, Flags, Args). prepare_run_args({eval, [Expr]}) -> {eval,Expr}; @@ -202,16 +245,6 @@ map(_F, []) -> map(F, [X|Rest]) -> [F(X) | map(F, Rest)]. -flags_to_atoms_again([]) -> - []; -flags_to_atoms_again([{F0,L0}|Rest]) -> - L = L0, - F = b2a(F0), - [{F,L}|flags_to_atoms_again(Rest)]; -flags_to_atoms_again([{F0}|Rest]) -> - F = b2a(F0), - [{F}|flags_to_atoms_again(Rest)]. - -spec code_path_choice() -> 'relaxed' | 'strict'. code_path_choice() -> case get_argument(code_path_choice) of @@ -224,6 +257,7 @@ code_path_choice() -> end. boot(Start,Flags,Args) -> + start_on_load_handler_process(), BootPid = do_boot(Flags,Start), State = #state{flags = Flags, args = Args, @@ -232,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. @@ -260,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) -> @@ -268,26 +318,17 @@ 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([]) -> "". halt_string(String, List) -> - HaltString = String ++ things_to_string(List), - if - length(HaltString)<199 -> HaltString; - true -> first198(HaltString, 198) - end. - -first198([H|T], N) when N>0 -> - [H|first198(T, N-1)]; -first198(_, 0) -> - []. + 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)). @@ -295,9 +336,9 @@ crash(String, List) -> -spec boot_loop(pid(), state()) -> no_return(). boot_loop(BootPid, State) -> receive - {BootPid,loaded,ModLoaded} -> - Loaded = State#state.loaded, - boot_loop(BootPid,State#state{loaded = [ModLoaded|Loaded]}); + {BootPid,loaded,NewlyLoaded} -> + Loaded = NewlyLoaded ++ State#state.loaded, + boot_loop(BootPid, State#state{loaded = Loaded}); {BootPid,started,KernelPid} -> boot_loop(BootPid, new_kernelpid(KernelPid, BootPid, State)); {BootPid,progress,started} -> @@ -336,12 +377,25 @@ boot_loop(BootPid, State) -> end. ensure_loaded(Module, Loaded) -> - File = concat([Module,objfile_extension()]), - case catch load_mod(Module,File) of - {ok, FullName} -> - {{module, Module}, [{Module, FullName}|Loaded]}; - Res -> - {Res, Loaded} + case erlang:module_loaded(Module) of + true -> + {{module, Module}, Loaded}; + false -> + do_ensure_loaded(Module, Loaded) + end. + +do_ensure_loaded(Module, Loaded) -> + File = atom_to_list(Module) ++ objfile_extension(), + case erl_prim_loader:get_file(File) of + {ok,BinCode,FullName} -> + case do_load_module(Module, BinCode) of + ok -> + {{module, Module}, [{Module, FullName}|Loaded]}; + error -> + {error, [{Module, FullName}|Loaded]} + end; + Error -> + {Error, Loaded} end. %% Tell subscribed processes the system has started. @@ -387,9 +441,6 @@ loop(State) -> Loaded = State#state.loaded, %% boot_loop but is handled here From ! {init,Loaded}, %% anyway. loop(State); - {From, {ensure_loaded, _}} -> - From ! {init, not_allowed}, - loop(State); Msg -> loop(handle_msg(Msg,State)) end. @@ -429,10 +480,17 @@ do_handle_msg(Msg,State) -> From ! {init,ok}, {new_state,State#state{subscribed = [Pid|Subscribed]}} end; + {From, {ensure_loaded, _}} -> + From ! {init, not_allowed}; 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 @@ -450,9 +508,9 @@ do_handle_msg(Msg,State) -> %%% ------------------------------------------------- make_permanent(Boot,Config,Flags0,State) -> - case set_flag('-boot',Boot,Flags0) of + case set_flag(boot, Boot, Flags0) of {ok,Flags1} -> - case set_flag('-config',Config,Flags1) of + case set_flag(config, Config, Flags1) of {ok,Flags} -> {ok,State#state{flags = Flags}}; Error -> @@ -492,6 +550,8 @@ 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(), boot(Start,Flags,Args); do_stop(reboot,_) -> halt(); @@ -504,8 +564,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 @@ -518,19 +588,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). + +get_kernelpid(Name,[{Name,Pid}|_Kernel]) -> Pid; +get_kernelpid(Name,[_|Kernel]) -> get_kernelpid(Name,Kernel); +get_kernelpid(_,_) -> false. -shutdown([{heart,_Pid}|Kernel],BootPid,Timer,State) -> +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), @@ -582,27 +659,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 zombies. -alive_processes() -> - [P || P <- processes(), erlang:is_process_alive(P)]. - -get_pids(Heart) -> - Pids = alive_processes(), - delete(Heart,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(_,_,[]) -> []. +%% All except system processes. +get_pids(Heart,Logger) -> + Pids = [P || P <- processes(), not erts_internal:is_system_process(P)], + delete(Heart,Logger,self(),Pids). + +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), @@ -632,9 +707,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), @@ -642,16 +717,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. @@ -693,17 +760,15 @@ sleep(T) -> receive after T -> ok end. %%% The loader shall run for ever! %%% ------------------------------------------------- -start_prim_loader(Init,Id,Pgm,Nodes,Path,{Pa,Pz}) -> - case erl_prim_loader:start(Id,Pgm,Nodes) of - {ok,Pid} when Path =:= false -> - InitPath = append(Pa,["."|Pz]), - erl_prim_loader:set_path(InitPath), - add_to_kernel(Init,Pid), - Pid; +start_prim_loader(Init, Path0, {Pa,Pz}) -> + Path = case Path0 of + false -> Pa ++ ["."|Pz]; + _ -> Path0 + end, + case erl_prim_loader:start() of {ok,Pid} -> erl_prim_loader:set_path(Path), - add_to_kernel(Init,Pid), - Pid; + add_to_kernel(Init, Pid); {error,Reason} -> erlang:display({"cannot start loader",Reason}), exit(Reason) @@ -717,13 +782,6 @@ add_to_kernel(Init,Pid) -> ok end. -prim_load_flags(Flags) -> - PortPgm = get_flag('-loader',Flags,<<"efile">>), - Hosts = get_flag_list('-hosts', Flags, []), - Id = get_flag('-id',Flags,none), - Path = get_flag_list('-path',Flags,false), - {PortPgm, Hosts, Id, Path}. - %%% ------------------------------------------------- %%% The boot process fetches a boot script and loads %%% all modules specified and starts spec. processes. @@ -736,46 +794,74 @@ do_boot(Flags,Start) -> do_boot(Init,Flags,Start) -> process_flag(trap_exit,true), - {Pgm0,Nodes,Id,Path} = prim_load_flags(Flags), - Root = b2s(get_flag('-root',Flags)), - PathFls = path_flags(Flags), - Pgm = b2s(Pgm0), - _Pid = start_prim_loader(Init,b2a(Id),Pgm,bs2as(Nodes), - bs2ss(Path),PathFls), + Root = get_root(Flags), + Path = get_flag_list(path, Flags, false), + {Pa,Pz} = PathFls = path_flags(Flags), + start_prim_loader(Init, bs2ss(Path), PathFls), BootFile = bootfile(Flags,Root), BootList = get_boot(BootFile,Root), - LoadMode = b2a(get_flag('-mode',Flags,false)), - Deb = b2a(get_flag('-init_debug',Flags,false)), + LoadMode = b2a(get_flag(mode, Flags, false)), + Deb = b2a(get_flag(init_debug, Flags, false)), catch ?ON_LOAD_HANDLER ! {init_debug_flag,Deb}, - BootVars = get_flag_args('-boot_var',Flags), - ParallelLoad = - (Pgm =:= "efile") and (erlang:system_info(thread_pool_size) > 0), + BootVars = get_boot_vars(Root, Flags), PathChoice = code_path_choice(), - eval_script(BootList,Init,PathFls,{Root,BootVars},Path, - {true,LoadMode,ParallelLoad},Deb,PathChoice), + Es = #es{init=Init,debug=Deb,path=Path,pa=Pa,pz=Pz, + path_choice=PathChoice, + prim_load=true,load_mode=LoadMode, + vars=BootVars}, + eval_script(BootList, Es), %% To help identifying Purify windows that pop up, %% print the node name into the Purify log. (catch erlang:system_info({purify, "Node: " ++ atom_to_list(node())})), - start_em(Start). + start_em(Start), + case b2a(get_flag(profile_boot,Flags,false)) of + false -> ok; + true -> + debug_profile_format_mfas(debug_profile_mfas()), + debug_profile_stop() + end, + ok. + +get_root(Flags) -> + case get_argument(root, Flags) of + {ok,[[Root]]} -> + Root; + _ -> + exit(no_or_multiple_root_variables) + end. + +get_boot_vars(Root, Flags) -> + BootVars = get_boot_vars_1(#{}, Flags), + RootKey = <<"ROOT">>, + BootVars#{RootKey=>Root}. + +get_boot_vars_1(Vars, [{boot_var,[Key,Value]}|T]) -> + get_boot_vars_1(Vars#{Key=>Value}, T); +get_boot_vars_1(_, [{boot_var,_}|_]) -> + exit(invalid_boot_var_argument); +get_boot_vars_1(Vars, [_|T]) -> + get_boot_vars_1(Vars, T); +get_boot_vars_1(Vars, []) -> + Vars. bootfile(Flags,Root) -> - b2s(get_flag('-boot',Flags,concat([Root,"/bin/start"]))). + b2s(get_flag(boot, Flags, Root++"/bin/start")). path_flags(Flags) -> - Pa = append(reverse(get_flag_args('-pa',Flags))), - Pz = append(get_flag_args('-pz',Flags)), + Pa = append(reverse(get_flag_args(pa, Flags))), + Pz = append(get_flag_args(pz, Flags)), {bs2ss(Pa),bs2ss(Pz)}. get_boot(BootFile0,Root) -> - BootFile = concat([BootFile0,".boot"]), + BootFile = BootFile0 ++ ".boot", case get_boot(BootFile) of {ok, CmdList} -> CmdList; not_found -> %% Check for default. - BootF = concat([Root,"/bin/",BootFile]), + BootF = Root ++ "/bin/" ++ BootFile, case get_boot(BootF) of {ok, CmdList} -> CmdList; @@ -809,91 +895,88 @@ get_boot(BootFile) -> %% boot process hangs (we want to ensure syncronicity). %% -eval_script([{progress,Info}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) -> - debug(Deb,{progress,Info}), +eval_script([{progress,Info}=Progress|T], #es{debug=Deb}=Es) -> + debug(Deb, Progress), init ! {self(),progress,Info}, - eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice); -eval_script([{preLoaded,_}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) -> - eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice); -eval_script([{path,Path}|CfgL],Init,{Pa,Pz},Vars,false,Ph,Deb,PathChoice) -> + eval_script(T, Es); +eval_script([{preLoaded,_}|T], #es{}=Es) -> + eval_script(T, Es); +eval_script([{path,Path}|T], #es{path=false,pa=Pa,pz=Pz, + path_choice=PathChoice, + vars=Vars}=Es) -> RealPath0 = make_path(Pa, Pz, Path, Vars), RealPath = patch_path(RealPath0, PathChoice), erl_prim_loader:set_path(RealPath), - eval_script(CfgL,Init,{Pa,Pz},Vars,false,Ph,Deb,PathChoice); -eval_script([{path,_}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) -> + eval_script(T, Es); +eval_script([{path,_}|T], #es{}=Es) -> %% Ignore, use the command line -path flag. - eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice); -eval_script([{kernel_load_completed}|CfgL],Init,PathFs,Vars,P,{_,embedded,Par},Deb,PathChoice) -> - eval_script(CfgL,Init,PathFs,Vars,P,{true,embedded,Par},Deb,PathChoice); -eval_script([{kernel_load_completed}|CfgL],Init,PathFs,Vars,P,{_,E,Par},Deb,PathChoice) -> - eval_script(CfgL,Init,PathFs,Vars,P,{false,E,Par},Deb,PathChoice); -eval_script([{primLoad,Mods}|CfgL],Init,PathFs,Vars,P,{true,E,Par},Deb,PathChoice) + eval_script(T, Es); +eval_script([{kernel_load_completed}|T], #es{load_mode=Mode}=Es0) -> + Es = case Mode of + embedded -> Es0; + _ -> Es0#es{prim_load=false} + end, + eval_script(T, Es); +eval_script([{primLoad,Mods}|T], #es{init=Init,prim_load=PrimLoad}=Es) when is_list(Mods) -> - if - Par =:= true -> - par_load_modules(Mods,Init); + case PrimLoad of true -> - load_modules(Mods) + load_modules(Mods, Init); + false -> + %% Do not load now, code_server does that dynamically! + ok end, - eval_script(CfgL,Init,PathFs,Vars,P,{true,E,Par},Deb,PathChoice); -eval_script([{primLoad,_Mods}|CfgL],Init,PathFs,Vars,P,{false,E,Par},Deb,PathChoice) -> - %% Do not load now, code_server does that dynamically! - eval_script(CfgL,Init,PathFs,Vars,P,{false,E,Par},Deb,PathChoice); -eval_script([{kernelProcess,Server,{Mod,Fun,Args}}|CfgL],Init, - PathFs,Vars,P,Ph,Deb,PathChoice) -> - debug(Deb,{start,Server}), - start_in_kernel(Server,Mod,Fun,Args,Init), - eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice); -eval_script([{apply,{Mod,Fun,Args}}|CfgL],Init,PathFs,Vars,P,Ph,Deb,PathChoice) -> - debug(Deb,{apply,{Mod,Fun,Args}}), - apply(Mod,Fun,Args), - eval_script(CfgL,Init,PathFs,Vars,P,Ph,Deb,PathChoice); -eval_script([],_,_,_,_,_,_,_) -> + eval_script(T, Es); +eval_script([{kernelProcess,Server,{Mod,Fun,Args}}|T], + #es{init=Init,debug=Deb}=Es) -> + debug(Deb, {start,Server}), + start_in_kernel(Server, Mod, Fun, Args, Init), + eval_script(T, Es); +eval_script([{apply,{Mod,Fun,Args}}=Apply|T], #es{debug=Deb}=Es) -> + debug(Deb, Apply), + apply(Mod, Fun, Args), + eval_script(T, Es); +eval_script([], #es{}) -> ok; -eval_script(What,_,_,_,_,_,_,_) -> +eval_script(What, #es{}) -> exit({'unexpected command in bootfile',What}). -load_modules([Mod|Mods]) -> - File = concat([Mod,objfile_extension()]), - {ok,Full} = load_mod(Mod,File), - init ! {self(),loaded,{Mod,Full}}, %% Tell init about loaded module - load_modules(Mods); -load_modules([]) -> +load_modules(Mods0, Init) -> + Mods = [M || M <- Mods0, not erlang:module_loaded(M)], + F = prepare_loading_fun(), + case erl_prim_loader:get_modules(Mods, F) of + {ok,{Prep0,[]}} -> + Prep = [Code || {_,{prepared,Code,_}} <- Prep0], + ok = erlang:finish_loading(Prep), + Loaded = [{Mod,Full} || {Mod,{_,_,Full}} <- Prep0], + Init ! {self(),loaded,Loaded}, + Beams = [{M,Beam,Full} || {M,{on_load,Beam,Full}} <- Prep0], + load_rest(Beams, Init); + {ok,{_,[_|_]=Errors}} -> + Ms = [M || {M,_} <- Errors], + exit({load_failed,Ms}) + end. + +load_rest([{Mod,Beam,Full}|T], Init) -> + do_load_module(Mod, Beam), + Init ! {self(),loaded,[{Mod,Full}]}, + load_rest(T, Init); +load_rest([], _) -> ok. -%%% An optimization: erl_prim_loader gets the chance of loading many -%%% files in parallel, using threads. This will reduce the seek times, -%%% and loaded code can be processed while other threads are waiting -%%% for the disk. The optimization is not tried unless the loader is -%%% "efile" and there is a non-empty pool of threads. -%%% -%%% Many threads are needed to get a good result, so it would be -%%% beneficial to load several applications in parallel. However, -%%% measurements show that the file system handles one directory at a -%%% time, regardless if parallel threads are created for files on -%%% several directories (a guess: writing the meta information when -%%% the file was last read ('mtime'), forces the file system to sync -%%% between directories). - -par_load_modules(Mods,Init) -> - Ext = objfile_extension(), - ModFiles = [{Mod,concat([Mod,Ext])} || Mod <- Mods, - not erlang:module_loaded(Mod)], - Self = self(), - Fun = fun(Mod, BinCode, FullName) -> - case catch load_mod_code(Mod, BinCode, FullName) of - {ok, _} -> - Init ! {Self,loaded,{Mod,FullName}}, - ok; - _EXIT -> - {error, Mod} - end - end, - case erl_prim_loader:get_files(ModFiles, Fun) of - ok -> - ok; - {error,Mod} -> - exit({'cannot load',Mod,get_files}) +prepare_loading_fun() -> + fun(Mod, FullName, Beam) -> + case erlang:prepare_loading(Mod, Beam) of + {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 + end end. make_path(Pa, Pz, Path, Vars) -> @@ -910,34 +993,25 @@ fix_path([Path|Ps], Vars) -> fix_path(_, _) -> []. -add_var("$ROOT/" ++ Path, {Root,_}) -> - concat([Root, "/", Path]); -add_var([$$|Path0], {_,VarList}) -> - {Var,Path} = extract_var(Path0,[]), - Value = b2s(get_var_value(list_to_binary(Var),VarList)), - concat([Value, "/", Path]); -add_var(Path, _) -> +add_var("$"++Path0, Vars) -> + {Var,Path} = extract_var(Path0, []), + Key = list_to_binary(Var), + case Vars of + #{Key:=Value0} -> + Value = b2s(Value0), + Value ++ "/" ++ Path; + _ -> + Error0 = "cannot expand $" ++ Var ++ " in bootfile", + Error = list_to_atom(Error0), + exit(Error) + end; +add_var(Path, _) -> Path. extract_var([$/|Path],Var) -> {reverse(Var),Path}; extract_var([H|T],Var) -> extract_var(T,[H|Var]); extract_var([],Var) -> {reverse(Var),[]}. -%% get_var_value(Var, [Vars]) where Vars == [atom()] -get_var_value(Var,[Vars|VarList]) -> - case get_var_val(Var,Vars) of - {ok, Value} -> - Value; - _ -> - get_var_value(Var,VarList) - end; -get_var_value(Var,[]) -> - exit(list_to_atom(concat(["cannot expand \$", Var, " in bootfile"]))). - -get_var_val(Var,[Var,Value|_]) -> {ok, Value}; -get_var_val(Var,[_,_|Vars]) -> get_var_val(Var,Vars); -get_var_val(_,_) -> false. - patch_path(Dirs, strict) -> Dirs; patch_path(Dirs, relaxed) -> @@ -1045,55 +1119,29 @@ 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()), ok; start_it([_|_]=MFA) -> - Ref = make_ref(), - case catch {Ref,case MFA of - [M] -> M:start(); - [M,F] -> M:F(); - [M,F|Args] -> M:F(Args) % Args is a list - end} of - {Ref,R} -> - R; - {'EXIT',Reason} -> - exit(Reason); - Other -> - throw(Other) + case MFA of + [M] -> M:start(); + [M,F] -> M:F(); + [M,F|Args] -> M:F(Args) % Args is a list end. -%% -%% Fetch a module and load it into the system. -%% -load_mod(Mod, File) -> - case erlang:module_loaded(Mod) of - false -> - case erl_prim_loader:get_file(File) of - {ok,BinCode,FullName} -> - load_mod_code(Mod, BinCode, FullName); - _ -> - exit({'cannot load',Mod,get_file}) - end; - _ -> % Already loaded. - {ok,File} - end. +%% Load a module. -load_mod_code(Mod, BinCode, FullName) -> - case erlang:module_loaded(Mod) of - false -> - case erlang:load_module(Mod, BinCode) of - {module,Mod} -> {ok,FullName}; - {error,on_load} -> - ?ON_LOAD_HANDLER ! {loaded,Mod}, - {ok,FullName}; - Other -> - exit({'cannot load',Mod,Other}) - end; - _ -> % Already loaded. - {ok,FullName} +do_load_module(Mod, BinCode) -> + case erlang:load_module(Mod, BinCode) of + {module,Mod} -> + ok; + {error,on_load} -> + ?ON_LOAD_HANDLER ! {loaded,Mod}, + ok; + _ -> + error end. %% -------------------------------------------------------- @@ -1104,7 +1152,7 @@ load_mod_code(Mod, BinCode, FullName) -> %% -------------------------------------------------------- shutdown_timer(Flags) -> - case get_flag('-shutdown_time',Flags,infinity) of + case get_flag(shutdown_time, Flags, infinity) of infinity -> self(); Time -> @@ -1154,14 +1202,10 @@ parse_boot_args([B|Bs], Ss, Fs, As) -> eval_arg -> {Expr,Rest} = get_args(Bs, []), parse_boot_args(Rest, [{eval, Expr}|Ss], Fs, As); - flag -> + {flag,A} -> {F,Rest} = get_args(Bs, []), - Fl = case F of - [] -> [B]; - FF -> [B,FF] - end, - parse_boot_args(Rest, Ss, - [list_to_tuple(Fl)|Fs], As); + Fl = {A,F}, + parse_boot_args(Rest, Ss, [Fl|Fs], As); arg -> parse_boot_args(Bs, Ss, Fs, [B|As]); end_args -> @@ -1175,12 +1219,8 @@ check(<<"-s">>) -> start_arg; check(<<"-run">>) -> start_arg2; check(<<"-eval">>) -> eval_arg; check(<<"--">>) -> end_args; -check(X) when is_binary(X) -> - case binary_to_list(X) of - [$-|_Rest] -> flag; - _Chars -> arg %Even empty atoms - end; -check(_X) -> arg. %This should never occur +check(<<"-",Flag/binary>>) -> {flag,b2a(Flag)}; +check(_) -> arg. get_args([B|Bs], As) -> case check(B) of @@ -1189,7 +1229,7 @@ get_args([B|Bs], As) -> start_arg2 -> {reverse(As), [B|Bs]}; eval_arg -> {reverse(As), [B|Bs]}; end_args -> {reverse(As), Bs}; - flag -> {reverse(As), [B|Bs]}; + {flag,_} -> {reverse(As), [B|Bs]}; arg -> get_args(Bs, [B|As]) end; @@ -1201,44 +1241,28 @@ get_args([], As) -> {reverse(As),[]}. %% atom() if a single arg was given. %% list(atom()) if several args were given. %% -get_flag(F,Flags,Default) -> - case catch get_flag(F,Flags) of - {'EXIT',_} -> - Default; - Value -> - Value - end. - -get_flag(F,Flags) -> - case search(F,Flags) of - {value,{F,[V]}} -> +get_flag(F, Flags, Default) -> + case lists:keyfind(F, 1, Flags) of + {F,[]} -> + true; + {F,[V]} -> V; - {value,{F,V}} -> + {F,V} -> V; - {value,{F}} -> % Flag given! - true; _ -> - exit(list_to_atom(concat(["no ",F," flag"]))) + Default end. %% %% Internal get_flag function, with default value. %% Return: list(atom()) %% -get_flag_list(F,Flags,Default) -> - case catch get_flag_list(F,Flags) of - {'EXIT',_} -> - Default; - Value -> - Value - end. - -get_flag_list(F,Flags) -> - case search(F,Flags) of - {value,{F,V}} -> +get_flag_list(F, Flags, Default) -> + case lists:keyfind(F, 1, Flags) of + {F,[_|_]=V} -> V; _ -> - exit(list_to_atom(concat(["no ",F," flag"]))) + Default end. %% @@ -1248,21 +1272,15 @@ get_flag_list(F,Flags) -> %% get_flag_args(F,Flags) -> get_flag_args(F,Flags,[]). -get_flag_args(F,[{F,V}|Flags],Acc) when is_list(V) -> - get_flag_args(F,Flags,[V|Acc]); get_flag_args(F,[{F,V}|Flags],Acc) -> - get_flag_args(F,Flags,[[V]|Acc]); + get_flag_args(F,Flags,[V|Acc]); get_flag_args(F,[_|Flags],Acc) -> get_flag_args(F,Flags,Acc); get_flag_args(_,[],Acc) -> reverse(Acc). get_arguments([{F,V}|Flags]) -> - [$-|Fl] = atom_to_list(F), - [{list_to_atom(Fl),to_strings(V)}|get_arguments(Flags)]; -get_arguments([{F}|Flags]) -> - [$-|Fl] = atom_to_list(F), - [{list_to_atom(Fl),[]}|get_arguments(Flags)]; + [{F,to_strings(V)}|get_arguments(Flags)]; get_arguments([]) -> []. @@ -1270,44 +1288,26 @@ to_strings([H|T]) when is_atom(H) -> [atom_to_list(H)|to_strings(T)]; to_strings([H|T]) when is_binary(H) -> [b2s(H)|to_strings(T)]; to_strings([]) -> []. -get_argument(Arg,Flags) -> - Args = get_arguments(Flags), - case get_argument1(Arg,Args) of - [] -> - error; - Value -> - {ok,Value} +get_argument(Arg, Flags) -> + case get_argument1(Arg, Flags) of + [] -> error; + Value -> {ok,Value} end. -get_argument1(Arg,[{Arg,V}|Args]) -> - [V|get_argument1(Arg,Args)]; -get_argument1(Arg,[_|Args]) -> - get_argument1(Arg,Args); -get_argument1(_,[]) -> +get_argument1(Arg, [{Arg,V}|Args]) -> + [to_strings(V)|get_argument1(Arg, Args)]; +get_argument1(Arg, [_|Args]) -> + get_argument1(Arg, Args); +get_argument1(_, []) -> []. set_argument([{Flag,_}|Flags],Flag,Value) -> [{Flag,[Value]}|Flags]; -set_argument([{Flag}|Flags],Flag,Value) -> - [{Flag,[Value]}|Flags]; set_argument([Item|Flags],Flag,Value) -> [Item|set_argument(Flags,Flag,Value)]; set_argument([],Flag,Value) -> [{Flag,[Value]}]. -concat([A|T]) when is_atom(A) -> - atom_to_list(A) ++ concat(T); -concat([C|T]) when is_integer(C), 0 =< C, C =< 255 -> - [C|concat(T)]; -concat([Bin|T]) when is_binary(Bin) -> - binary_to_list(Bin) ++ concat(T); -concat([S|T]) -> - S ++ concat(T); -concat([]) -> - []. - -append(L, Z) -> L ++ Z. - append([E]) -> E; append([H|T]) -> H ++ append(T); @@ -1322,13 +1322,6 @@ reverse([A, B]) -> reverse([A, B | L]) -> lists:reverse(L, [B, A]). % BIF -search(Key, [H|_T]) when is_tuple(H), element(1, H) =:= Key -> - {value, H}; -search(Key, [_|T]) -> - search(Key, T); -search(_Key, []) -> - false. - -spec objfile_extension() -> nonempty_string(). objfile_extension() -> ".beam". @@ -1404,3 +1397,64 @@ run_on_load_handlers([M|Ms], Debug) -> end end; run_on_load_handlers([], _) -> ok. + + +%% debug profile (light variant of eprof) +debug_profile_start() -> + _ = erlang:trace_pattern({'_','_','_'},true,[call_time]), + _ = erlang:trace_pattern(on_load,true,[call_time]), + _ = erlang:trace(all,true,[call]), + ok. + +debug_profile_stop() -> + _ = erlang:trace_pattern({'_','_','_'},false,[call_time]), + _ = erlang:trace_pattern(on_load,false,[call_time]), + _ = erlang:trace(all,false,[call]), + ok. + +debug_profile_mfas() -> + _ = erlang:trace_pattern({'_','_','_'},pause,[call_time]), + _ = erlang:trace_pattern(on_load,pause,[call_time]), + MFAs = collect_loaded_mfas() ++ erlang:system_info(snifs), + collect_mfas(MFAs,[]). + +%% debug_profile_format_mfas should be called at the end of the boot phase +%% so all pertinent modules should be loaded at that point. +debug_profile_format_mfas(MFAs0) -> + MFAs = lists:sort(MFAs0), + lists:foreach(fun({{Us,C},{M,F,A}}) -> + Str = io_lib:format("~w:~w/~w", [M,F,A]), + io:format(standard_error,"~55s - ~6w : ~w us~n", [Str,C,Us]) + end, MFAs), + ok. + +collect_loaded_mfas() -> + Ms = [M || M <- [element(1, Mi) || Mi <- code:all_loaded()]], + collect_loaded_mfas(Ms,[]). + +collect_loaded_mfas([],MFAs) -> MFAs; +collect_loaded_mfas([M|Ms],MFAs0) -> + MFAs = [{M,F,A} || {F,A} <- M:module_info(functions)], + collect_loaded_mfas(Ms,MFAs ++ MFAs0). + + +collect_mfas([], Info) -> Info; +collect_mfas([MFA|MFAs],Info) -> + case erlang:trace_info(MFA,call_time) of + {call_time, []} -> + collect_mfas(MFAs,Info); + {call_time, false} -> + collect_mfas(MFAs,Info); + {call_time, Data} -> + case collect_mfa(MFA,Data,0,0) of + {{0,_},_} -> + %% ignore mfas with zero time + collect_mfas(MFAs,Info); + MfaData -> + collect_mfas(MFAs,[MfaData|Info]) + end + end. + +collect_mfa(Mfa,[],Count,Time) -> {{Time,Count},Mfa}; +collect_mfa(Mfa,[{_Pid,C,S,Us}|Data],Count,Time) -> + collect_mfa(Mfa,Data,Count + C,Time + S * 1000000 + Us). diff --git a/erts/preloaded/src/otp_ring0.erl b/erts/preloaded/src/otp_ring0.erl index 2ccf142f30..62a60fffe2 100644 --- a/erts/preloaded/src/otp_ring0.erl +++ b/erts/preloaded/src/otp_ring0.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2010. All Rights Reserved. +%% Copyright Ericsson AB 2000-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% 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 958a79a1da..e4b1560517 100644 --- a/erts/preloaded/src/prim_eval.S +++ b/erts/preloaded/src/prim_eval.S @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2017. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -25,7 +26,7 @@ {attributes, []}. -{labels, 10}. +{labels, 14}. {function, 'receive', 2, 2}. @@ -35,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}}. @@ -52,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_eval.erl b/erts/preloaded/src/prim_eval.erl index ec5af8c138..22e924f9e9 100644 --- a/erts/preloaded/src/prim_eval.erl +++ b/erts/preloaded/src/prim_eval.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% diff --git a/erts/preloaded/src/prim_file.erl b/erts/preloaded/src/prim_file.erl index 34679404a2..6d85868183 100644 --- a/erts/preloaded/src/prim_file.erl +++ b/erts/preloaded/src/prim_file.erl @@ -1,164 +1,63 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2013. All Rights Reserved. +%% Copyright Ericsson AB 2000-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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_file). -%% Interface module to the file driver. - - - -%%% Interface towards a single file's contents. Uses ?FD_DRV. - -%% 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([on_load/0]). -%% Specialized file operations --export([open/1, open/3]). --export([read_file/1, read_file/2, write_file/2]). --export([ipread_s32bu_p32bu/3]). +-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]). +-export([read_file/1, write_file/2]). -%%% Interface towards file system and metadata. Uses ?DRV. +-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]). -%% 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]). +-export([list_dir/1, list_dir_all/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"). +-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]). +-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'. @@ -184,1315 +83,745 @@ 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}. +%% -%% 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}. +%% 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({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. +on_load() -> + ok = erlang:load_nif(atom_to_list(?MODULE), 0). -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 +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 +-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(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} - 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). - % 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}. - -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 79ff013c77..8169943dde 100644 --- a/erts/preloaded/src/prim_inet.erl +++ b/erts/preloaded/src/prim_inet.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2000-2013. All Rights Reserved. +%% Copyright Ericsson AB 2000-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -28,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]). @@ -69,11 +70,13 @@ open(Protocol, Family, Type) -> open(Protocol, Family, Type, Opts) -> open(Protocol, Family, Type, Opts, ?INET_REQ_OPEN, []). +%% FDOPEN(tcp|udp|sctp, inet|inet6|local, stream|dgram|seqpacket, integer()) + fdopen(Protocol, Family, Type, Fd) when is_integer(Fd) -> fdopen(Protocol, Family, Type, Fd, true). fdopen(Protocol, Family, Type, Fd, Bound) - when is_integer(Fd), Bound == true orelse Bound == false -> + when is_integer(Fd), is_boolean(Bound) -> open(Protocol, Family, Type, [], ?INET_REQ_FDOPEN, [?int32(Fd), enc_value_2(bool, Bound)]). @@ -103,8 +106,9 @@ open(Protocol, Family, Type, Opts, Req, Data) -> error:system_limit -> {error, system_limit} end. -enc_family(inet) -> ?INET_AF_INET; -enc_family(inet6) -> ?INET_AF_INET6. +enc_family(inet) -> ?INET_AF_INET; +enc_family(inet6) -> ?INET_AF_INET6; +enc_family(local) -> ?INET_AF_LOCAL. enc_type(stream) -> ?INET_TYPE_STREAM; enc_type(dgram) -> ?INET_TYPE_DGRAM; @@ -127,37 +131,18 @@ drv2protocol(_) -> undefined. %% TODO: shutdown equivalent for SCTP %% shutdown(S, read) when is_port(S) -> - shutdown_2(S, 0); + shutdown_1(S, 0); shutdown(S, write) when is_port(S) -> shutdown_1(S, 1); shutdown(S, read_write) when is_port(S) -> shutdown_1(S, 2). shutdown_1(S, How) -> - case subscribe(S, [subs_empty_out_q]) of - {ok,[{subs_empty_out_q,N}]} when N > 0 -> - shutdown_pend_loop(S, N); %% wait for pending output to be sent - _Other -> ok - end, - shutdown_2(S, How). - -shutdown_2(S, How) -> case ctl_cmd(S, ?TCP_REQ_SHUTDOWN, [How]) of {ok, []} -> ok; {error,_}=Error -> Error end. -shutdown_pend_loop(S, N0) -> - receive - {empty_out_q,S} -> ok - after ?INET_CLOSE_TIMEOUT -> - case getstat(S, [send_pend]) of - {ok,[{send_pend,N0}]} -> ok; - {ok,[{send_pend,N}]} -> shutdown_pend_loop(S, N); - _ -> ok - end - end. - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% CLOSE(insock()) -> ok @@ -165,11 +150,16 @@ shutdown_pend_loop(S, N0) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% close(S) when is_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 + case getopt(S, linger) of + {ok,{true,0}} -> + close_port(S); _ -> - 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 end. close_pend_loop(S, N) -> @@ -202,41 +192,52 @@ close_port(S) -> %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -bind(S,IP,Port) when is_port(S), is_integer(Port), Port >= 0, Port =< 65535 -> - case ctl_cmd(S,?INET_REQ_BIND,enc_value(set, addr, {IP,Port})) of - {ok, [P1,P0]} -> {ok, ?u16(P1, P0)}; - {error,_}=Error -> Error - end; - %% Multi-homed "bind": sctp_bindx(). The Op is 'add' or 'remove'. %% If no addrs are specified, it just does nothing. %% Function returns {ok, S} on success, unlike TCP/UDP "bind": -bind(S, Op, Addrs) when is_port(S), is_list(Addrs) -> - case Op of - add -> - bindx(S, 1, Addrs); - remove -> - bindx(S, 0, Addrs); - _ -> {error, einval} +bind(S, add, Addrs) when is_port(S), is_list(Addrs) -> + bindx(S, 1, Addrs); +bind(S, remove, Addrs) when is_port(S), is_list(Addrs) -> + bindx(S, 0, Addrs); +bind(S, Addr, _) when is_port(S), tuple_size(Addr) =:= 2 -> + case type_value(set, addr, Addr) of + true -> + case ctl_cmd(S,?INET_REQ_BIND,enc_value(set, addr, Addr)) of + {ok, [P1,P0]} -> {ok, ?u16(P1, P0)}; + {error, _} = Error -> Error + end; + false -> + {error, einval} end; -bind(_, _, _) -> {error, einval}. +bind(S, IP, Port) -> + bind(S, {IP, Port}, 0). bindx(S, AddFlag, Addrs) -> case getprotocol(S) of sctp -> - %% Really multi-homed "bindx". Stringified args: - %% [AddFlag, (AddrBytes see enc_value_2(addr,X))+]: - Args = - [?int8(AddFlag)| - [enc_value(set, addr, {IP,Port}) || - {IP, Port} <- Addrs]], - case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of - {ok,_} -> {ok, S}; - {error,_}=Error -> Error + case bindx_check_addrs(Addrs) of + true -> + %% Really multi-homed "bindx". Stringified args: + %% [AddFlag, (AddrBytes see enc_value_2(addr,X))+]: + Args = + [?int8(AddFlag)| + [enc_value(set, addr, Addr) || Addr <- Addrs]], + case ctl_cmd(S, ?SCTP_REQ_BINDX, Args) of + {ok, _} -> {ok, S}; + {error, _}=Error -> Error + end; + false -> + {error, einval} end; - _ -> {error, einval} + _ -> + {error, einval} end. +bindx_check_addrs([Addr|Addrs]) -> + type_value(set, addr, Addr) andalso bindx_check_addrs(Addrs); +bindx_check_addrs([]) -> + true. + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% CONNECT(insock(), IP, Port [,Timeout]) -> ok | {error, Reason} @@ -245,7 +246,7 @@ bindx(S, AddFlag, Addrs) -> %% if timeout is given: %% timeout < 0 -> infinity %% 0 -> immediate connect (mostly works for loopback) -%% > 0 -> wait for timout ms if not connected then +%% > 0 -> wait for timeout ms if not connected then %% return {error, timeout} %% %% ASYNC_CONNECT(insock(), IP, Port, Timeout) -> {ok, S, Ref} | {error, Reason} @@ -255,14 +256,24 @@ bindx(S, AddFlag, Addrs) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% For TCP, UDP or SCTP sockets. %% -connect(S, IP, Port) -> connect0(S, IP, Port, -1). -connect(S, IP, Port, infinity) -> connect0(S, IP, Port, -1); -connect(S, IP, Port, Time) -> connect0(S, IP, Port, Time). +connect(S, IP, Port) -> + connect(S, IP, Port, infinity). +%% +connect(S, Addr, _, Time) when is_port(S), tuple_size(Addr) =:= 2 -> + case type_value(set, addr, Addr) of + true when Time =:= infinity -> + connect0(S, Addr, -1); + true when is_integer(Time) -> + connect0(S, Addr, Time); + false -> + {error, einval} + end; +connect(S, IP, Port, Time) -> + connect(S, {IP, Port}, 0, Time). -connect0(S, IP, Port, Time) when is_port(S), Port > 0, Port =< 65535, - is_integer(Time) -> - case async_connect(S, IP, Port, Time) of +connect0(S, Addr, Time) -> + case async_connect0(S, Addr, Time) of {ok, S, Ref} -> receive {inet_async, S, Ref, Status} -> @@ -271,23 +282,41 @@ connect0(S, IP, Port, Time) when is_port(S), Port > 0, Port =< 65535, Error -> Error end. + +async_connect(S, Addr, _, Time) when is_port(S), tuple_size(Addr) =:= 2 -> + case type_value(set, addr, Addr) of + true when Time =:= infinity -> + async_connect0(S, Addr, -1); + true when is_integer(Time) -> + async_connect0(S, Addr, Time); + false -> + {error, einval} + end; +%% async_connect(S, IP, Port, Time) -> - case ctl_cmd(S, ?INET_REQ_CONNECT, - [enc_time(Time),?int16(Port),ip_to_bytes(IP)]) of + async_connect(S, {IP, Port}, 0, Time). + +async_connect0(S, Addr, Time) -> + case ctl_cmd( + S, ?INET_REQ_CONNECT, + [enc_time(Time),enc_value(set, addr, Addr)]) + of {ok, [R1,R0]} -> {ok, S, ?u16(R1,R0)}; - {error,_}=Error -> Error + {error, _}=Error -> Error end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% -%% 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: %% timeout < 0 -> infinity %% 0 -> immediate accept (poll) -%% > 0 -> wait for timout ms for accept if no accept then +%% > 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) %% @@ -298,17 +327,22 @@ async_connect(S, IP, Port, Time) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% For TCP sockets only. %% -accept(L) -> accept0(L, -1). +accept(L) -> accept0(L, -1, []). + +accept(L, infinity) -> accept0(L, -1, []); +accept(L, FamilyOpts) when is_list(FamilyOpts) -> accept0(L, -1, FamilyOpts); +accept(L, Time) -> accept0(L, Time, []). -accept(L, infinity) -> accept0(L, -1); -accept(L, Time) -> accept0(L, Time). +accept(L, infinity, FamilyOpts) -> accept0(L, -1, FamilyOpts); +accept(L, Time, FamilyOpts) -> accept0(L, Time, FamilyOpts). -accept0(L, Time) when is_port(L), is_integer(Time) -> +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; @@ -316,15 +350,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 -> {ok, S}; - 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) -> @@ -413,20 +454,34 @@ send(S, Data) -> %% "sendto" is for UDP. IP and Port are set by the caller to 0 if the socket %% is known to be connected. -sendto(S, IP, Port, Data) when is_port(S), Port >= 0, Port =< 65535 -> - ?DBG_FORMAT("prim_inet:sendto(~p, ~p, ~p, ~p)~n", [S,IP,Port,Data]), - try erlang:port_command(S, [?int16(Port),ip_to_bytes(IP),Data]) of - true -> - receive - {inet_reply,S,Reply} -> - ?DBG_FORMAT("prim_inet:sendto() -> ~p~n", [Reply]), - Reply - end - catch - error:_ -> - ?DBG_FORMAT("prim_inet:sendto() -> {error,einval}~n", []), - {error,einval} - end. +sendto(S, Addr, _, Data) when is_port(S), tuple_size(Addr) =:= 2 -> + case type_value(set, addr, Addr) of + true -> + ?DBG_FORMAT("prim_inet:sendto(~p, ~p, ~p)~n", [S,Addr,Data]), + try + erlang:port_command(S, [enc_value(set, addr, Addr),Data]) + of + true -> + receive + {inet_reply,S,Reply} -> + ?DBG_FORMAT( + "prim_inet:sendto() -> ~p~n", [Reply]), + Reply + end + catch + error:_ -> + ?DBG_FORMAT( + "prim_inet:sendto() -> {error,einval}~n", []), + {error,einval} + end; + false -> + ?DBG_FORMAT( + "prim_inet:sendto() -> {error,einval}~n", []), + {error,einval} + end; +sendto(S, IP, Port, Data) -> + sendto(S, {IP, Port}, 0, Data). + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% @@ -449,6 +504,55 @@ 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() -> + sendfile_1(S, FileHandle, Offset, Length); + {connected, Pid} when Pid =/= self() -> + {error, not_owner}; + _Other -> + {error, einval} + end. + +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 @@ -497,29 +601,46 @@ async_recv(S, Length, Time) -> %% oriented: preserved here only for API compatibility. %% recvfrom(S, Length) -> - recvfrom0(S, Length, -1). + recvfrom(S, Length, infinity). -recvfrom(S, Length, infinity) -> +recvfrom(S, Length, infinity) when is_port(S) -> recvfrom0(S, Length, -1); -recvfrom(S, Length, Time) when is_integer(Time), Time < 16#ffffffff -> - recvfrom0(S, Length, Time); -recvfrom(_, _, _) -> {error,einval}. +recvfrom(S, Length, Time) when is_port(S) -> + if + is_integer(Time), 0 =< Time, Time < 16#ffffffff -> + recvfrom0(S, Length, Time); + true -> + {error, einval} + end. recvfrom0(S, Length, Time) - when is_port(S), is_integer(Length), Length >= 0, Length =< 16#ffffffff -> + when is_integer(Length), 0 =< Length, Length =< 16#ffffffff -> case ctl_cmd(S, ?PACKET_REQ_RECV,[enc_time(Time),?int32(Length)]) of {ok,[R1,R0]} -> Ref = ?u16(R1,R0), receive % Success, UDP: - {inet_async, S, Ref, {ok, [F,P1,P0 | AddrData]}} -> - {IP,Data} = get_ip(F, AddrData), - {ok, {IP, ?u16(P1,P0), Data}}; + {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}}; + {{IP, Port}, Data} -> + {ok, {IP, Port, Data}} + end; % Success, SCTP: {inet_async, S, Ref, {ok, {[F,P1,P0 | Addr], AncData, DE}}} -> - {IP, _} = get_ip(F, Addr), - {ok, {IP, ?u16(P1,P0), AncData, DE}}; + {IP, _} = get_ip(F, Addr), + {ok, {IP, ?u16(P1, P0), AncData, DE}}; % Back-end error: {inet_async, S, Ref, Error={error, _}} -> @@ -538,21 +659,26 @@ recvfrom0(_, _, _) -> {error,einval}. peername(S) when is_port(S) -> case ctl_cmd(S, ?INET_REQ_PEER, []) of - {ok, [F, P1,P0 | Addr]} -> - {IP, _} = get_ip(F, Addr), - {ok, { IP, ?u16(P1, P0) }}; - {error,_}=Error -> Error + {ok, [F | Addr]} -> + {A, _} = get_addr(F, Addr), + {ok, A}; + {error, _} = Error -> Error end. -setpeername(S, {IP,Port}) when is_port(S) -> - case ctl_cmd(S, ?INET_REQ_SETPEER, [?int16(Port),ip_to_bytes(IP)]) of - {ok,[]} -> ok; - {error,_}=Error -> Error - end; setpeername(S, undefined) when is_port(S) -> case ctl_cmd(S, ?INET_REQ_SETPEER, []) of - {ok,[]} -> ok; - {error,_}=Error -> Error + {ok, []} -> ok; + {error, _} = Error -> Error + end; +setpeername(S, Addr) when is_port(S) -> + case type_value(set, addr, Addr) of + true -> + case ctl_cmd(S, ?INET_REQ_SETPEER, enc_value(set, addr, Addr)) of + {ok, []} -> ok; + {error, _} = Error -> Error + end; + false -> + {error, einval} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -593,21 +719,28 @@ peernames(S, AssocId) sockname(S) when is_port(S) -> case ctl_cmd(S, ?INET_REQ_NAME, []) of - {ok, [F, P1, P0 | Addr]} -> - {IP, _} = get_ip(F, Addr), - {ok, { IP, ?u16(P1, P0) }}; - {error,_}=Error -> Error + {ok, [F | Addr]} -> + {A, _} = get_addr(F, Addr), + {ok, A}; + {error, _} = Error -> Error end. -setsockname(S, {IP,Port}) when is_port(S) -> - case ctl_cmd(S, ?INET_REQ_SETNAME, [?int16(Port),ip_to_bytes(IP)]) of - {ok,[]} -> ok; - {error,_}=Error -> Error - end; setsockname(S, undefined) when is_port(S) -> case ctl_cmd(S, ?INET_REQ_SETNAME, []) of - {ok,[]} -> ok; - {error,_}=Error -> Error + {ok, []} -> ok; + {error, _} = Error -> Error + end; +setsockname(S, Addr) when is_port(S) -> + case type_value(set, addr, Addr) of + true -> + case + ctl_cmd(S, ?INET_REQ_SETNAME, enc_value(set, addr, Addr)) + of + {ok, []} -> ok; + {error, _} = Error -> Error + end; + false -> + {error, einval} end. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -1135,6 +1268,12 @@ enc_opt(sndbuf) -> ?INET_OPT_SNDBUF; 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(multicast_if) -> ?UDP_OPT_MULTICAST_IF; enc_opt(multicast_ttl) -> ?UDP_OPT_MULTICAST_TTL; @@ -1159,7 +1298,10 @@ enc_opt(delay_send) -> ?INET_LOPT_TCP_DELAY_SEND; enc_opt(packet_size) -> ?INET_LOPT_PACKET_SIZE; enc_opt(read_packets) -> ?INET_LOPT_READ_PACKETS; 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; @@ -1192,7 +1334,13 @@ dec_opt(?INET_OPT_SNDBUF) -> sndbuf; dec_opt(?INET_OPT_RCVBUF) -> recbuf; 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(?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; @@ -1216,7 +1364,10 @@ dec_opt(?INET_LOPT_TCP_DELAY_SEND) -> delay_send; dec_opt(?INET_LOPT_PACKET_SIZE) -> packet_size; dec_opt(?INET_LOPT_READ_PACKETS) -> read_packets; 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. @@ -1264,6 +1415,12 @@ type_opt_1(sndbuf) -> int; 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(ipv6_v6only) -> bool; %% multicast @@ -1298,6 +1455,7 @@ type_opt_1(packet) -> {httph_bin,?TCP_PB_HTTPH_BIN}, {ssl, ?TCP_PB_SSL_TLS}, % obsolete {ssl_tls, ?TCP_PB_SSL_TLS}]}; +type_opt_1(line_delimiter) -> int; type_opt_1(mode) -> {enum,[{list, ?INET_MODE_LIST}, {binary, ?INET_MODE_BINARY}]}; @@ -1315,6 +1473,8 @@ type_opt_1(delay_send) -> bool; 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 @@ -1503,14 +1663,49 @@ type_value_2(uint8, X) when X band 16#ff =:= X -> true; type_value_2(time, infinity) -> true; type_value_2(time, X) when is_integer(X), X >= 0 -> true; type_value_2(ip,{A,B,C,D}) when ?ip(A,B,C,D) -> true; +%% type_value_2(addr, {any,Port}) -> type_value_2(uint16, Port); type_value_2(addr, {loopback,Port}) -> type_value_2(uint16, Port); -type_value_2(addr, {{A,B,C,D},Port}) when ?ip(A,B,C,D) -> +type_value_2(addr, {IP,_} = Addr) when tuple_size(IP) =:= 4 -> + type_value_2(addr, {inet,Addr}); +type_value_2(addr, {IP,_} = Addr) when tuple_size(IP) =:= 8 -> + type_value_2(addr, {inet6,Addr}); +type_value_2(addr, {Local,_}) when is_list(Local); is_binary(Local) -> + type_value_2(addr, {local,Local}); +%% +type_value_2(addr, {Family,{Tag,Port}}) + when (Family =:= inet orelse Family =:= inet6) andalso + (Tag =:= any orelse Tag =:= loopback) -> + type_value_2(uint16, Port); +type_value_2(addr, {inet,{{A,B,C,D},Port}}) + when ?ip(A,B,C,D) -> type_value_2(uint16, Port); -type_value_2(addr, {{A,B,C,D,E,F,G,H},Port}) when ?ip6(A,B,C,D,E,F,G,H) -> +type_value_2(addr, {inet6,{{A,B,C,D,E,F,G,H},Port}}) + when ?ip6(A,B,C,D,E,F,G,H) -> type_value_2(uint16, Port); +type_value_2(addr, {local,Addr}) -> + if + is_binary(Addr) -> + byte_size(Addr) =< 255; + true -> + try + %% We either get a badarg from byte_size + %% or from characters_to_binary + byte_size( + unicode:characters_to_binary( + Addr, file:native_name_encoding())) + of + N when N =< 255 -> + true; + _ -> + false + catch error:badarg -> + false + end + end; +%% type_value_2(ether,[X1,X2,X3,X4,X5,X6]) when ?ether(X1,X2,X3,X4,X5,X6) -> true; type_value_2({enum,List}, Enum) -> @@ -1618,6 +1813,7 @@ enc_value_2(time, Val) -> ?int32(Val); enc_value_2(ip,{A,B,C,D}) -> [A,B,C,D]; enc_value_2(ip, any) -> [0,0,0,0]; enc_value_2(ip, loopback) -> [127,0,0,1]; +%% enc_value_2(addr, {any,Port}) -> [?INET_AF_ANY|?int16(Port)]; enc_value_2(addr, {loopback,Port}) -> @@ -1626,6 +1822,35 @@ enc_value_2(addr, {IP,Port}) when tuple_size(IP) =:= 4 -> [?INET_AF_INET,?int16(Port)|ip4_to_bytes(IP)]; enc_value_2(addr, {IP,Port}) when tuple_size(IP) =:= 8 -> [?INET_AF_INET6,?int16(Port)|ip6_to_bytes(IP)]; +enc_value_2(addr, {File,_}) when is_list(File); is_binary(File) -> + [?INET_AF_LOCAL,iolist_size(File)|File]; +%% +enc_value_2(addr, {inet,{any,Port}}) -> + [?INET_AF_INET,?int16(Port),0,0,0,0]; +enc_value_2(addr, {inet,{loopback,Port}}) -> + [?INET_AF_INET,?int16(Port),127,0,0,1]; +enc_value_2(addr, {inet,{IP,Port}}) -> + [?INET_AF_INET,?int16(Port)|ip4_to_bytes(IP)]; +enc_value_2(addr, {inet6,{any,Port}}) -> + [?INET_AF_INET6,?int16(Port),0,0,0,0,0,0,0,0]; +enc_value_2(addr, {inet6,{loopback,Port}}) -> + [?INET_AF_INET6,?int16(Port),0,0,0,0,0,0,0,1]; +enc_value_2(addr, {inet6,{IP,Port}}) -> + [?INET_AF_INET6,?int16(Port)|ip6_to_bytes(IP)]; +enc_value_2(addr, {local,Addr}) -> + %% A binary is passed as is, but anything else will be + %% regarded as a filename and therefore encoded according to + %% the current system filename encoding mode. + Bin = + if + is_binary(Addr) -> + Addr; + true -> + unicode:characters_to_binary( + Addr, file:native_name_encoding()) + end, + [?INET_AF_LOCAL,byte_size(Bin),Bin]; +%% enc_value_2(ether, [_,_,_,_,_,_]=Xs) -> Xs; enc_value_2(sockaddr, any) -> [?INET_AF_ANY]; @@ -1702,6 +1927,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}; @@ -2256,9 +2486,6 @@ utf8_to_characters(Bs, U, 0) -> utf8_to_characters([B|Bs], U, N) when ((B band 16#3F) bor 16#80) =:= B -> utf8_to_characters(Bs, (U bsl 6) bor (B band 16#3F), N-1). -ip_to_bytes(IP) when tuple_size(IP) =:= 4 -> ip4_to_bytes(IP); -ip_to_bytes(IP) when tuple_size(IP) =:= 8 -> ip6_to_bytes(IP). - ip4_to_bytes({A,B,C,D}) -> [A band 16#ff, B band 16#ff, C band 16#ff, D band 16#ff]. @@ -2268,18 +2495,32 @@ ip6_to_bytes({A,B,C,D,E,F,G,H}) -> get_addrs([]) -> []; -get_addrs([F,P1,P0|Addr]) -> - {IP,Addrs} = get_ip(F, Addr), - [{IP,?u16(P1, P0)}|get_addrs(Addrs)]. - -get_ip(?INET_AF_INET, Addr) -> get_ip4(Addr); -get_ip(?INET_AF_INET6, Addr) -> get_ip6(Addr). +get_addrs([F|Addrs]) -> + {Addr,Rest} = get_addr(F, Addrs), + [Addr|get_addrs(Rest)]. + +get_addr(?INET_AF_LOCAL, [N|Addr]) -> + {A,Rest} = lists:split(N, Addr), + {{local,iolist_to_binary(A)},Rest}; +get_addr(?INET_AF_UNSPEC, Rest) -> + {{unspec,<<>>},Rest}; +get_addr(?INET_AF_UNDEFINED, Rest) -> + {{undefined,<<>>},Rest}; +get_addr(Family, [P1,P0|Addr]) -> + {IP,Rest} = get_ip(Family, Addr), + {{IP,?u16(P1, P0)},Rest}. + +get_ip(?INET_AF_INET, Addr) -> + get_ip4(Addr); +get_ip(?INET_AF_INET6, Addr) -> + get_ip6(Addr). get_ip4([A,B,C,D | T]) -> {{A,B,C,D},T}. get_ip6([X1,X2,X3,X4,X5,X6,X7,X8,X9,X10,X11,X12,X13,X14,X15,X16 | T]) -> { { ?u16(X1,X2),?u16(X3,X4),?u16(X5,X6),?u16(X7,X8), - ?u16(X9,X10),?u16(X11,X12),?u16(X13,X14),?u16(X15,X16)}, T}. + ?u16(X9,X10),?u16(X11,X12),?u16(X13,X14),?u16(X15,X16)}, + T }. %% Control command diff --git a/erts/preloaded/src/prim_zip.erl b/erts/preloaded/src/prim_zip.erl index 1d5ab52a24..ca5cfec0e3 100644 --- a/erts/preloaded/src/prim_zip.erl +++ b/erts/preloaded/src/prim_zip.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% @@ -73,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}. @@ -88,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 @@ -105,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/zip_internal.hrl b/erts/preloaded/src/zip_internal.hrl index a8f7b1f1b7..2769ca152d 100644 --- a/erts/preloaded/src/zip_internal.hrl +++ b/erts/preloaded/src/zip_internal.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2009. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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% %% diff --git a/erts/preloaded/src/zlib.erl b/erts/preloaded/src/zlib.erl index df7b2e6198..6f53e67901 100644 --- a/erts/preloaded/src/zlib.erl +++ b/erts/preloaded/src/zlib.erl @@ -1,36 +1,50 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2013. All Rights Reserved. +%% Copyright Ericsson AB 2003-2018. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% 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 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% 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(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, - 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]). +-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). @@ -53,114 +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(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'. + +-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. + +%% Private data types. -%% Auxiliary data types of the file --type zlevel() :: 'none' | 'default' | 'best_compression' | 'best_speed' - | 0..9. -type zmethod() :: 'deflated'. --type zwindowbits() :: -15..-9 | 9..47. --type zmemlevel() :: 1..9. --type zstrategy() :: 'default' | 'filtered' | 'huffman_only' | 'rle'. + +-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(), @@ -168,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(), @@ -204,148 +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) -> + enqueue_input(Z, Data), + inflateChunk(Z). + +-spec inflateChunk(Z) -> Decompressed | {more, Decompressed} when + Z :: zstream(), + Decompressed :: iolist(). +inflateChunk(Z) -> + 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(), @@ -353,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 @@ -371,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) @@ -395,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 @@ -410,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 @@ -440,90 +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}) - 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). |