1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2000-2018. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%%----------------------------------------------------------------------
22%%% File    : file_server.erl
23%%% Author  : Raimo Niskanen <raimo@erix.ericsson.se>
24%%% Purpose : A simple file server
25%%% Created : 13 Oct 2000 by Raimo Niskanen <raimo@erix.ericsson.se>
26%%%----------------------------------------------------------------------
27
28-module(file_server).
29
30-behaviour(gen_server).
31
32%% External exports
33-export([format_error/1]).
34-export([start/0, start_link/0, stop/0]).
35
36%% gen_server callbacks
37-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
38	 terminate/2, code_change/3]).
39
40-define(FILE_IO_SERVER_TABLE, file_io_servers).
41
42-define(FILE_SERVER, file_server_2).      % Registered name
43-define(FILE_IO_SERVER, file_io_server).  % Module
44-define(PRIM_FILE, prim_file).            % Module
45
46%%%----------------------------------------------------------------------
47%%% API
48%%%----------------------------------------------------------------------
49format_error({_Line, ?MODULE, Reason}) ->
50    io_lib:format("~w", [Reason]);
51format_error({_Line, Mod, Reason}) ->
52    Mod:format_error(Reason);
53format_error(ErrorId) ->
54    erl_posix_msg:message(ErrorId).
55
56start() -> do_start(start).
57start_link() -> do_start(start_link).
58
59stop() ->
60    gen_server:call(?FILE_SERVER, stop, infinity).
61
62%%%----------------------------------------------------------------------
63%%% Callback functions from gen_server
64%%%----------------------------------------------------------------------
65
66-type state() :: term().	% Internal type
67
68%%----------------------------------------------------------------------
69%% Func: init/1
70%% Returns: {ok, State}          |
71%%          {ok, State, Timeout} |
72%%          ignore               |
73%%          {stop, Reason}
74%%----------------------------------------------------------------------
75
76-spec init([]) -> {'ok', state()}.
77
78init([]) ->
79    process_flag(trap_exit, true),
80    ?FILE_IO_SERVER_TABLE =  ets:new(?FILE_IO_SERVER_TABLE, [named_table]),
81    {ok, undefined}.
82
83%%----------------------------------------------------------------------
84%% Func: handle_call/3
85%% Returns: {reply, Reply, State}          |
86%%          {reply, Reply, State, Timeout} |
87%%          {noreply, State}               |
88%%          {noreply, State, Timeout}      |
89%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
90%%          {stop, Reason, State}            (terminate/2 is called)
91%%----------------------------------------------------------------------
92
93-spec handle_call(term(), term(), state()) ->
94        {'noreply', state()} |
95	{'reply', 'eof' | 'ok' | {'error', term()} | {'ok', term()}, state()} |
96	{'stop', 'normal', 'stopped', state()}.
97
98handle_call({open, Name, ModeList}, {Pid, _Tag} = _From, State)
99  when is_list(ModeList) ->
100    Child = ?FILE_IO_SERVER:start_link(Pid, Name, ModeList),
101    case Child of
102	{ok, P} when is_pid(P) ->
103	    ets:insert(?FILE_IO_SERVER_TABLE, {P, Name});
104	_ ->
105	    ok
106    end,
107    {reply, Child, State};
108
109handle_call({open, _Name, _Mode}, _From, State) ->
110    {reply, {error, einval}, State};
111
112handle_call({read_file, Name}, _From, State) ->
113    {reply, ?PRIM_FILE:read_file(Name), State};
114
115handle_call({write_file, Name, Bin}, _From, State) ->
116    {reply, ?PRIM_FILE:write_file(Name, Bin), State};
117
118handle_call({set_cwd, Name}, _From, State) ->
119    {reply, ?PRIM_FILE:set_cwd(Name), State};
120
121handle_call({delete, Name}, _From, State) ->
122    {reply, ?PRIM_FILE:delete(Name), State};
123
124handle_call({rename, Fr, To}, _From, State) ->
125    {reply, ?PRIM_FILE:rename(Fr, To), State};
126
127handle_call({make_dir, Name}, _From, State) ->
128    {reply, ?PRIM_FILE:make_dir(Name), State};
129
130handle_call({del_dir, Name}, _From, State) ->
131    {reply, ?PRIM_FILE:del_dir(Name), State};
132
133handle_call({list_dir, Name}, _From, State) ->
134    {reply, ?PRIM_FILE:list_dir(Name), State};
135handle_call({list_dir_all, Name}, _From, State) ->
136    {reply, ?PRIM_FILE:list_dir_all(Name), State};
137
138handle_call(get_cwd, _From, State) ->
139    {reply, ?PRIM_FILE:get_cwd(), State};
140handle_call({get_cwd}, _From, State) ->
141    {reply, ?PRIM_FILE:get_cwd(), State};
142handle_call({get_cwd, Name}, _From, State) ->
143    {reply, ?PRIM_FILE:get_cwd(Name), State};
144
145handle_call({read_file_info, Name}, _From, State) ->
146    {reply, ?PRIM_FILE:read_file_info(Name), State};
147
148handle_call({read_file_info, Name, Opts}, _From, State) ->
149    {reply, ?PRIM_FILE:read_file_info(Name, Opts), State};
150
151handle_call({altname, Name}, _From, State) ->
152    {reply, ?PRIM_FILE:altname(Name), State};
153
154handle_call({write_file_info, Name, Info}, _From, State) ->
155    {reply, ?PRIM_FILE:write_file_info(Name, Info), State};
156
157handle_call({write_file_info, Name, Info, Opts}, _From, State) ->
158    {reply, ?PRIM_FILE:write_file_info(Name, Info, Opts), State};
159
160handle_call({read_link_info, Name}, _From, State) ->
161    {reply, ?PRIM_FILE:read_link_info(Name), State};
162
163handle_call({read_link_info, Name, Opts}, _From, State) ->
164    {reply, ?PRIM_FILE:read_link_info(Name, Opts), State};
165
166handle_call({read_link, Name}, _From, State) ->
167    {reply, ?PRIM_FILE:read_link(Name), State};
168handle_call({read_link_all, Name}, _From, State) ->
169    {reply, ?PRIM_FILE:read_link_all(Name), State};
170
171handle_call({make_link, Old, New}, _From, State) ->
172    {reply, ?PRIM_FILE:make_link(Old, New), State};
173
174handle_call({make_symlink, Old, New}, _From, State) ->
175    {reply, ?PRIM_FILE:make_symlink(Old, New), State};
176
177handle_call({copy, SourceName, SourceOpts, DestName, DestOpts, Length},
178	    _From, State) ->
179    Reply =
180	case ?PRIM_FILE:open(SourceName, [read, binary | SourceOpts]) of
181	    {ok, Source} ->
182		SourceReply =
183		    case ?PRIM_FILE:open(DestName,
184					 [write, binary | DestOpts]) of
185			{ok, Dest} ->
186			    DestReply =
187				?PRIM_FILE:copy(Source, Dest, Length),
188			    ?PRIM_FILE:close(Dest),
189			    DestReply;
190			{error, _} = Error ->
191			    Error
192		    end,
193		?PRIM_FILE:close(Source),
194		SourceReply;
195	    {error, _} = Error ->
196		Error
197	end,
198    {reply, Reply, State};
199
200handle_call(stop, _From, State) ->
201    {stop, normal, stopped, State};
202
203handle_call(Request, From, State) ->
204    error_logger:error_msg("handle_call(~tp, ~tp, _)", [Request, From]),
205    {noreply, State}.
206
207%%----------------------------------------------------------------------
208%% Func: handle_cast/2
209%% Returns: {noreply, State}          |
210%%          {noreply, State, Timeout} |
211%%          {stop, Reason, State}            (terminate/2 is called)
212%%----------------------------------------------------------------------
213
214-spec handle_cast(term(), state()) -> {'noreply', state()}.
215
216handle_cast(Msg, State) ->
217    error_logger:error_msg("handle_cast(~tp, _)", [Msg]),
218    {noreply, State}.
219
220%%----------------------------------------------------------------------
221%% Func: handle_info/2
222%% Returns: {noreply, State}          |
223%%          {noreply, State, Timeout} |
224%%          {stop, Reason, State}            (terminate/2 is called)
225%%----------------------------------------------------------------------
226
227-spec handle_info(term(), state()) ->
228        {'noreply', state()}.
229
230handle_info({'EXIT', Pid, _Reason}, State) when is_pid(Pid) ->
231    ets:delete(?FILE_IO_SERVER_TABLE, Pid),
232    {noreply, State};
233
234handle_info(Info, State) ->
235    error_logger:error_msg("handle_Info(~tp, _)", [Info]),
236    {noreply, State}.
237
238%%----------------------------------------------------------------------
239%% Func: terminate/2
240%% Purpose: Shutdown the server
241%% Returns: any (ignored by gen_server)
242%%----------------------------------------------------------------------
243
244-spec terminate(term(), state()) -> 'ok'.
245
246terminate(_Reason, _State) ->
247    ok.
248
249%%----------------------------------------------------------------------
250%% Func: code_change/3
251%% Purpose: Convert process state when code is changed
252%% Returns: {ok, NewState}
253%%----------------------------------------------------------------------
254
255-spec code_change(term(), state(), term()) -> {'ok', state()}.
256
257code_change(_OldVsn, State, _Extra) ->
258    {ok, State}.
259
260%%%----------------------------------------------------------------------
261%%% Internal functions
262%%%----------------------------------------------------------------------
263
264%%% The basic file server and start-up.
265%%%
266%%% The file server just handles the open command/message and acts as a
267%%% router for messages between the port and the file processes. If a
268%%% file process terminates we close the associated file.
269
270%% Start = start | start_link
271do_start(Start) ->
272    case init:get_argument(master) of
273	error ->
274	    gen_server:Start({local,?FILE_SERVER}, ?MODULE, [], []);
275	{ok, [[Node]]} ->
276	    do_start(Start, list_to_atom(Node), ?FILE_SERVER);
277	X ->
278	    {error, {get_argument, master, X}}
279    end.
280
281%% Should mimic gen_server:Start
282do_start(Start, Node, Name) ->
283    case rpc:call(Node, erlang, whereis, [Name]) of
284	Filer when is_pid(Filer); Filer =:= undefined ->
285	    case catch do_start_slave(Start, Filer, Name) of
286		{'EXIT', Reason} ->
287		    {error, Reason};
288		Result ->
289		    Result
290	    end;
291	Other ->
292	    {error, {no_master, Other}}
293    end.
294
295%% May exit upon failure, return {ok, SlavePid} if all well.
296do_start_slave(start_link, Filer, Name) ->
297    Self = self(),
298    Token = make_ref(),
299    Slave = spawn_link(fun() -> relay_start(Self, Token, Filer, Name) end),
300    receive
301	{started, Token} ->
302	    {ok, Slave}
303    end;
304do_start_slave(start, Filer, Name) ->
305    Self = self(),
306    Token = make_ref(),
307    Slave = spawn(fun() -> relay_start(Self, Token, Filer, Name) end),
308    SlaveMonitor = erlang:monitor(process, Slave),
309    receive
310	{started, Token} ->
311	    erlang:demonitor(SlaveMonitor, [flush]),
312	    {ok, Slave};
313	{'DOWN', SlaveMonitor, _, _, Reason} ->
314	    exit(Reason)
315    end.
316
317%% We have the relay process file internal.
318%% We do not need to load slave as a mandatory module
319%% during system startup.
320
321relay_start(Parent, Token, Filer, Name) when is_pid(Filer) ->
322    case catch register(Name, self()) of
323	true ->
324	    ok;
325	_ ->
326	    exit({already_started, whereis(Name)})
327    end,
328    %% This will fail towards an R5 node or older, Filer is a pid()
329    FilerMonitor = erlang:monitor(process, Filer),
330    process_flag(trap_exit, true),
331    Parent ! {started, Token},
332    relay_loop(Parent, Filer, FilerMonitor);
333relay_start(Parent, Token, undefined, _Name) ->
334    %% Dummy process to keep kernel supervisor happy
335    process_flag(trap_exit, true),
336    Parent ! {started, Token},
337    receive
338	{'EXIT', Parent, Reason} ->
339	    exit(Reason)
340    end.
341
342relay_loop(Parent, Filer, FilerMonitor) ->
343    receive
344	{'DOWN', FilerMonitor, _, _, Reason} ->
345	    exit(Reason);
346	{'EXIT', Parent, Reason} ->
347	    exit(Reason);
348        Msg ->
349            Filer ! Msg
350    end,
351    relay_loop(Parent, Filer, FilerMonitor).
352