diff options
Diffstat (limited to 'lib/kernel/src/file_server.erl')
-rw-r--r-- | lib/kernel/src/file_server.erl | 325 |
1 files changed, 325 insertions, 0 deletions
diff --git a/lib/kernel/src/file_server.erl b/lib/kernel/src/file_server.erl new file mode 100644 index 0000000000..74f2fb94a9 --- /dev/null +++ b/lib/kernel/src/file_server.erl @@ -0,0 +1,325 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. 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. +%% +%% %CopyrightEnd% +%% + +%%%---------------------------------------------------------------------- +%%% File : file_server.erl +%%% Author : Raimo Niskanen <[email protected]> +%%% Purpose : A simple file server +%%% Created : 13 Oct 2000 by Raimo Niskanen <[email protected]> +%%%---------------------------------------------------------------------- + +-module(file_server). + +-behaviour(gen_server). + +%% External exports +-export([format_error/1]). +-export([start/0, start_link/0, stop/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(FILE_IO_SERVER_TABLE, file_io_servers). + +-define(FILE_SERVER, file_server_2). % Registered name +-define(FILE_IO_SERVER, file_io_server). % Module +-define(PRIM_FILE, prim_file). % Module + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +format_error({_Line, ?MODULE, Reason}) -> + io_lib:format("~w", [Reason]); +format_error({_Line, Mod, Reason}) -> + Mod:format_error(Reason); +format_error(ErrorId) -> + erl_posix_msg:message(ErrorId). + +start() -> do_start(start). +start_link() -> do_start(start_link). + +stop() -> + gen_server:call(?FILE_SERVER, stop, infinity). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_server +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%---------------------------------------------------------------------- +init([]) -> + process_flag(trap_exit, true), + case ?PRIM_FILE:start() of + {ok, Handle} -> + ets:new(?FILE_IO_SERVER_TABLE, [named_table]), + {ok, Handle}; + {error, Reason} -> + {stop, Reason} + end. + +%%---------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_call({open, Name, ModeList}, {Pid, _Tag} = _From, Handle) + when is_list(ModeList) -> + Child = ?FILE_IO_SERVER:start_link(Pid, Name, ModeList), + case Child of + {ok, P} when is_pid(P) -> + ets:insert(?FILE_IO_SERVER_TABLE, {P, Name}); + _ -> + ok + end, + {reply, Child, Handle}; + +handle_call({open, _Name, _Mode}, _From, Handle) -> + {reply, {error, einval}, Handle}; + +handle_call({read_file, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:read_file(Name), Handle}; + +handle_call({write_file, Name, Bin}, _From, Handle) -> + {reply, ?PRIM_FILE:write_file(Name, Bin), Handle}; + +handle_call({set_cwd, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:set_cwd(Handle, Name), Handle}; + +handle_call({delete, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:delete(Handle, Name), Handle}; + +handle_call({rename, Fr, To}, _From, Handle) -> + {reply, ?PRIM_FILE:rename(Handle, Fr, To), Handle}; + +handle_call({make_dir, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:make_dir(Handle, Name), Handle}; + +handle_call({del_dir, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:del_dir(Handle, Name), Handle}; + +handle_call({list_dir, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:list_dir(Handle, Name), Handle}; + +handle_call(get_cwd, _From, Handle) -> + {reply, ?PRIM_FILE:get_cwd(Handle), Handle}; +handle_call({get_cwd}, _From, Handle) -> + {reply, ?PRIM_FILE:get_cwd(Handle), Handle}; +handle_call({get_cwd, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:get_cwd(Handle, Name), Handle}; + +handle_call({read_file_info, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:read_file_info(Handle, Name), Handle}; + +handle_call({altname, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:altname(Handle, Name), Handle}; + +handle_call({write_file_info, Name, Info}, _From, Handle) -> + {reply, ?PRIM_FILE:write_file_info(Handle, Name, Info), Handle}; + +handle_call({read_link_info, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:read_link_info(Handle, Name), Handle}; + +handle_call({read_link, Name}, _From, Handle) -> + {reply, ?PRIM_FILE:read_link(Handle, Name), Handle}; + +handle_call({make_link, Old, New}, _From, Handle) -> + {reply, ?PRIM_FILE:make_link(Handle, Old, New), Handle}; + +handle_call({make_symlink, Old, New}, _From, Handle) -> + {reply, ?PRIM_FILE:make_symlink(Handle, Old, New), Handle}; + +handle_call({copy, SourceName, SourceOpts, DestName, DestOpts, Length}, + _From, Handle) -> + Reply = + case ?PRIM_FILE:open(SourceName, [read, binary | SourceOpts]) of + {ok, Source} -> + SourceReply = + case ?PRIM_FILE:open(DestName, + [write, binary | DestOpts]) of + {ok, Dest} -> + DestReply = + ?PRIM_FILE:copy(Source, Dest, Length), + ?PRIM_FILE:close(Dest), + DestReply; + {error, _} = Error -> + Error + end, + ?PRIM_FILE:close(Source), + SourceReply; + {error, _} = Error -> + Error + end, + {reply, Reply, Handle}; + +handle_call(stop, _From, Handle) -> + {stop, normal, stopped, Handle}; + +handle_call(Request, From, Handle) -> + error_logger:error_msg("handle_call(~p, ~p, _)", [Request, From]), + {noreply, Handle}. + +%%---------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- +handle_cast(Msg, State) -> + error_logger:error_msg("handle_cast(~p, _)", [Msg]), + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%---------------------------------------------------------------------- + +handle_info({'EXIT', Pid, _Reason}, Handle) when is_pid(Pid) -> + ets:delete(?FILE_IO_SERVER_TABLE, Pid), + {noreply, Handle}; + +handle_info({'EXIT', Handle, _Reason}, Handle) -> + error_logger:error_msg("Port controlling ~w terminated in ~w", + [?FILE_SERVER, ?MODULE]), + {stop, normal, Handle}; + +handle_info(Info, State) -> + error_logger:error_msg("handle_Info(~p, _)", [Info]), + {noreply, State}. + +%%---------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%---------------------------------------------------------------------- +terminate(_Reason, Handle) -> + ?PRIM_FILE:stop(Handle). + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +%%% The basic file server and start-up. +%%% +%%% The file server just handles the open command/message and acts as a +%%% router for messages between the port and the file processes. If a +%%% file process terminates we close the associated file. + +%% Start = start | start_link +do_start(Start) -> + case init:get_argument(master) of + error -> + gen_server:Start({local,?FILE_SERVER}, ?MODULE, [], []); + {ok, [[Node]]} -> + do_start(Start, list_to_atom(Node), ?FILE_SERVER); + X -> + {error, {get_argument, master, X}} + end. + +%% Should mimic gen_server:Start +do_start(Start, Node, Name) -> + case rpc:call(Node, erlang, whereis, [Name]) of + Filer when is_pid(Filer); Filer =:= undefined -> + case catch do_start_slave(Start, Filer, Name) of + {'EXIT', Reason} -> + {error, Reason}; + Result -> + Result + end; + Other -> + {error, {no_master, Other}} + end. + +%% May exit upon failure, return {ok, SlavePid} if all well. +do_start_slave(start_link, Filer, Name) -> + Self = self(), + Token = make_ref(), + Slave = spawn_link(fun() -> relay_start(Self, Token, Filer, Name) end), + receive + {started, Token} -> + {ok, Slave} + end; +do_start_slave(start, Filer, Name) -> + Self = self(), + Token = make_ref(), + Slave = spawn(fun() -> relay_start(Self, Token, Filer, Name) end), + SlaveMonitor = erlang:monitor(process, Slave), + receive + {started, Token} -> + erlang:demonitor(SlaveMonitor), + receive {'DOWN', SlaveMonitor, _, _, _} -> ok after 0 -> ok end, + {ok, Slave}; + {'DOWN', SlaveMonitor, _, _, Reason} -> + exit(Reason) + end. + +%% We have the relay process file internal. +%% We do not need to load slave as a mandatory module +%% during system startup. + +relay_start(Parent, Token, Filer, Name) when is_pid(Filer) -> + case catch register(Name, self()) of + true -> + ok; + _ -> + exit({already_started, whereis(Name)}) + end, + %% This will fail towards an R5 node or older, Filer is a pid() + FilerMonitor = erlang:monitor(process, Filer), + process_flag(trap_exit, true), + Parent ! {started, Token}, + relay_loop(Parent, Filer, FilerMonitor); +relay_start(Parent, Token, undefined, _Name) -> + %% Dummy process to keep kernel supervisor happy + process_flag(trap_exit, true), + Parent ! {started, Token}, + receive + {'EXIT', Parent, Reason} -> + exit(Reason) + end. + +relay_loop(Parent, Filer, FilerMonitor) -> + receive + {'DOWN', FilerMonitor, _, _, Reason} -> + exit(Reason); + {'EXIT', Parent, Reason} -> + exit(Reason); + Msg -> + Filer ! Msg + end, + relay_loop(Parent, Filer, FilerMonitor). |