%% %% 2004-2007 %% Ericsson AB, 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. %% %% The Initial Developer of the Original Code is Ericsson AB. %% %% %%% Description: SFTP server daemon -module(ssh_sftpd). -behaviour(gen_server). %%-------------------------------------------------------------------- %% Include files %%-------------------------------------------------------------------- -include_lib("kernel/include/file.hrl"). -include("ssh.hrl"). -include("ssh_xfer.hrl"). -define(DEFAULT_TIMEOUT, 5000). %%-------------------------------------------------------------------- %% External exports -export([listen/1, listen/2, listen/3, stop/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -record(state, { xf, % [{channel,ssh_xfer states}...] cwd, % current dir (on first connect) root, % root dir remote_channel, % remote channel pending, % binary() file_handler, % atom() - callback module file_state, % state for the file callback module handles % list of open handles %% handle is either {, directory, {Path, unread|eof}} or %% {, file, {Path, IoDevice}} }). %%==================================================================== %% API %%==================================================================== %%-------------------------------------------------------------------- %% Function: listen() -> Pid | {error,Error} %% Description: Starts the server %%-------------------------------------------------------------------- listen(Port) -> listen(any, Port, []). listen(Port, Options) -> listen(any, Port, Options). listen(Addr, Port, Options) -> ssh_cm:listen(fun() -> {ok,Pid} = gen_server:start_link(?MODULE, [Options], []), Pid end, Addr, Port, Options). %%-------------------------------------------------------------------- %% Function: stop(Pid) -> ok %% Description: Stops the listener %%-------------------------------------------------------------------- stop(Pid) -> ssh_cli:stop(Pid). %%==================================================================== %% gen_server callbacks %%==================================================================== %%-------------------------------------------------------------------- %% Function: init(Args) -> {ok, State} | %% {ok, State, Timeout} | %% ignore | %% {stop, Reason} %% Description: Initiates the server %%-------------------------------------------------------------------- init([Options]) -> {FileMod, FS0} = case proplists:get_value(file_handler, Options, {ssh_sftpd_file,[]}) of {F, S} -> {F, S}; F -> {F, []} end, {{ok, Default}, FS1} = FileMod:get_cwd(FS0), CWD = proplists:get_value(cwd, Options, Default), Root = proplists:get_value(root, Options, ""), State = #state{cwd = CWD, root = Root, handles = [], pending = <<>>, file_handler = FileMod, file_state = FS1}, {ok, State}. %%-------------------------------------------------------------------- %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} | %% {reply, Reply, State, Timeout} | %% {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, Reply, State} | %% {stop, Reason, State} %% Description: Handling call messages %%-------------------------------------------------------------------- handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling cast messages %%-------------------------------------------------------------------- handle_cast(_Msg, State) -> {noreply, State}. %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | %% {noreply, State, Timeout} | %% {stop, Reason, State} %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({ssh_cm, CM, {open, Channel, RemoteChannel, _}}, State) -> XF = #ssh_xfer{vsn = 5, ext = [], cm = CM, channel = Channel}, State1 = State#state{xf = XF, remote_channel = RemoteChannel}, {noreply, State1}; handle_info({ssh_cm, CM, {data, Channel, Type, Data}}, State) -> ssh_cm:adjust_window(CM, Channel, size(Data)), State1 = handle_data(Type, Data, State), {noreply, State1}; handle_info({ssh_cm, CM, {subsystem, _Channel, WantsReply, "sftp"}}, State) -> CM = (State#state.xf)#ssh_xfer.cm, % hmmm going through xf... case WantsReply of true -> CM ! {ssh_cm, self(), {success, State#state.remote_channel}} end, {noreply, State}; %% The client has terminated the session handle_info({ssh_cm, _, {eof, Channel}}, State = #state{xf = #ssh_xfer{channel = Channel}}) -> {stop, normal, State}; handle_info(_Info, State) -> ?dbg(true, "handle_info: Info=~p State=~p\n", [_Info, State]), {noreply, State}. handle_data(0, <>, State = #state{pending = <<>>}) -> <> = Msg, NewState = handle_op(Op, ReqId, Data, State), case Rest of <<>> -> NewState; _ -> handle_data(0, Rest, NewState) end; handle_data(0, Data, State = #state{pending = <<>>}) -> State#state{pending = Data}; handle_data(Type, Data, State = #state{pending = Pending}) -> handle_data(Type, <>, State#state{pending = <<>>}); handle_data(_, Data, State) -> error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]), State. handle_op(?SSH_FXP_INIT, Version, B, State) when is_binary(B) -> XF = State#state.xf, Vsn = lists:min([XF#ssh_xfer.vsn, Version]), XF1 = XF#ssh_xfer{vsn = Vsn}, ssh_xfer:xf_send_reply(XF1, ?SSH_FXP_VERSION, <>), State#state{xf = XF1}; handle_op(?SSH_FXP_REALPATH, ReqId, <>, State) -> RelPath = binary_to_list(RPath), AbsPath = relate_file_name(RelPath, State), ?dbg(true, "handle_op ?SSH_FXP_REALPATH: RelPath=~p AbsPath=~p\n", [RelPath, AbsPath]), XF = State#state.xf, Attr = #ssh_xfer_attr{type=directory}, ssh_xfer:xf_send_name(XF, ReqId, AbsPath, Attr), State; handle_op(?SSH_FXP_OPENDIR, ReqId, <>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> RelPath = binary_to_list(RPath), AbsPath = relate_file_name(RelPath, State0), XF = State0#state.xf, {IsDir, FS1} = FileMod:is_dir(AbsPath, FS0), State1 = State0#state{file_state = FS1}, case IsDir of false -> ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NOT_A_DIRECTORY, "Not a directory"), State1; true -> add_handle(State1, XF, ReqId, directory, {RelPath,unread}) end; handle_op(?SSH_FXP_READDIR, ReqId, <>, State) -> XF = State#state.xf, case get_handle(State#state.handles, BinHandle) of {_Handle, directory, {_RelPath, eof}} -> ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_EOF), State; {Handle, directory, {RelPath, _}} -> read_dir(State, XF, ReqId, Handle, RelPath); _ -> ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_INVALID_HANDLE), State end; handle_op(?SSH_FXP_CLOSE, ReqId, <>, State = #state{handles = Handles, xf = XF, file_handler = FileMod, file_state = FS0}) -> case get_handle(Handles, BinHandle) of {Handle, Type, T} -> FS1 = case Type of file -> close_our_file(T, FileMod, FS0); _ -> FS0 end, ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_OK), State#state{handles = lists:keydelete(Handle, 1, Handles), file_state = FS1}; _ -> ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_INVALID_HANDLE), State end; handle_op(?SSH_FXP_LSTAT, ReqId, Data, State) -> stat((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State, read_link_info); handle_op(?SSH_FXP_STAT, ReqId, Data, State) -> stat((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State, read_file_info); handle_op(?SSH_FXP_FSTAT, ReqId, Data, State) -> fstat((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State); handle_op(?SSH_FXP_OPEN, ReqId, Data, State) -> open((State#state.xf)#ssh_xfer.vsn, ReqId, Data, State); handle_op(?SSH_FXP_READ, ReqId, <>, State) -> case get_handle(State#state.handles, BinHandle) of {_Handle, file, {_Path, IoDevice}} -> read_file(ReqId, IoDevice, Offset, Len, State); _ -> ssh_xfer:xf_send_status(State#state.xf, ReqId, ?SSH_FX_INVALID_HANDLE), State end; handle_op(?SSH_FXP_WRITE, ReqId, <>, State) -> case get_handle(State#state.handles, BinHandle) of {_Handle, file, {_Path, IoDevice}} -> write_file(ReqId, IoDevice, Offset, Data, State); _ -> ssh_xfer:xf_send_status(State#state.xf, ReqId, ?SSH_FX_INVALID_HANDLE), State end; handle_op(?SSH_FXP_READLINK, ReqId, <>, State = #state{file_handler = FileMod, file_state = FS0}) -> RelPath = binary_to_list(BPath), AbsPath = relate_file_name(RelPath, State), {Res, FS1} = FileMod:read_link(AbsPath, FS0), case Res of {ok, NewPath} -> ssh_xfer:xf_send_name(State#state.xf, ReqId, NewPath, #ssh_xfer_attr{type=regular}); {error, Error} -> ssh_xfer:xf_send_status(State#state.xf, ReqId, ssh_xfer:encode_erlang_status(Error)) end, State#state{file_state = FS1}; handle_op(?SSH_FXP_SETSTAT, ReqId, <>, State0) -> Path = relate_file_name(BPath, State0), {Status, State1} = set_stat(Attr, Path, State0), send_status(Status, ReqId, State1); handle_op(?SSH_FXP_MKDIR, ReqId, <>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> Path = relate_file_name(BPath, State0), {Res, FS1} = FileMod:make_dir(Path, FS0), State1 = State0#state{file_state = FS1}, case Res of ok -> {_, State2} = set_stat(Attr, Path, State1), send_status(ok, ReqId, State2); {error, Error} -> send_status({error, Error}, ReqId, State1) end; handle_op(?SSH_FXP_FSETSTAT, ReqId, <>, State0 = #state{handles = Handles}) -> case get_handle(Handles, BinHandle) of {_Handle, _Type, {Path,_}} -> {Status, State1} = set_stat(Attr, Path, State0), send_status(Status, ReqId, State1); _ -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, ?SSH_FX_INVALID_HANDLE), State0 end; handle_op(?SSH_FXP_REMOVE, ReqId, <>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> Path = relate_file_name(BPath, State0), %% case FileMod:is_dir(Path) of %% This version 6 we still have ver 5 %% true -> %% ssh_xfer:xf_send_status(State#state.xf, ReqId, %% ?SSH_FX_FILE_IS_A_DIRECTORY); %% false -> {Status, FS1} = FileMod:delete(Path, FS0), State1 = State0#state{file_state = FS1}, send_status(Status, ReqId, State1); %%end; handle_op(?SSH_FXP_RMDIR, ReqId, <>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> Path = relate_file_name(BPath, State0), {Status, FS1} = FileMod:del_dir(Path, FS0), State1 = State0#state{file_state = FS1}, send_status(Status, ReqId, State1); handle_op(?SSH_FXP_RENAME, ReqId, Bin = <>, State = #state{xf = #ssh_xfer{vsn = 3}}) -> handle_op(?SSH_FXP_RENAME, ReqId, <>, State); handle_op(?SSH_FXP_RENAME, ReqId, <>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> Path = relate_file_name(BPath, State0), Path2 = relate_file_name(BPath2, State0), case Flags band ?SSH_FXP_RENAME_ATOMIC of 0 -> case Flags band ?SSH_FXP_RENAME_OVERWRITE of 0 -> {Res, FS1} = FileMod:read_link_info(Path2, FS0), State1 = State0#state{file_state = FS1}, case Res of {ok, _Info} -> ssh_xfer:xf_send_status(State1#state.xf, ReqId, ?SSH_FX_FILE_ALREADY_EXISTS), State1; _ -> rename(Path, Path2, ReqId, State1) end; _ -> rename(Path, Path2, ReqId, State0) end; _ -> ssh_xfer:xf_send_status(State0#state.xf, ReqId, ?SSH_FX_OP_UNSUPPORTED), State0 end; handle_op(?SSH_FXP_SYMLINK, ReqId, <>, State0 = #state{file_handler = FileMod, file_state = FS0}) -> Path = relate_file_name(BPath, State0), Path2 = relate_file_name(BPath2, State0), {Status, FS1} = FileMod:make_symlink(Path2, Path, FS0), State1 = State0#state{file_state = FS1}, send_status(Status, ReqId, State1). %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> void() %% Description: This function is called by a gen_server when it is about to %% terminate. It should be the opposite of Module:init/1 and do any necessary %% cleaning up. When it returns, the gen_server terminates with Reason. %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_, #state{handles=Handles, file_handler=FileMod, file_state=FS}) -> CloseFun = fun({_, file, {_, Fd}}, FS0) -> {_Res, FS1} = FileMod:close(Fd, FS0), FS1; (_, FS0) -> FS0 end, lists:foldl(CloseFun, FS, Handles), ok. %%-------------------------------------------------------------------- %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- new_handle([], H) -> H; new_handle([{N, _} | Rest], H) when N > H -> new_handle(Rest, N+1); new_handle([_ | Rest], H) -> new_handle(Rest, H). add_handle(State, XF, ReqId, Type, DirFileTuple) -> Handles = State#state.handles, Handle = new_handle(Handles, 0), ssh_xfer:xf_send_handle(XF, ReqId, integer_to_list(Handle)), State#state{handles = [{Handle, Type, DirFileTuple} | Handles]}. get_handle(Handles, BinHandle) -> case (catch list_to_integer(binary_to_list(BinHandle))) of I when integer(I) -> case lists:keysearch(I, 1, Handles) of {value, T} -> T; false -> error end; _ -> error end. %%% read_dir/5: read directory, send names, and return new state read_dir(State0 = #state{file_handler = FileMod, file_state = FS0}, XF, ReqId, Handle, RelPath) -> AbsPath = relate_file_name(RelPath, State0), ?dbg(true, "read_dir: AbsPath=~p\n", [AbsPath]), {Res, FS1} = FileMod:list_dir(AbsPath, FS0), case Res of {ok, Files} -> {NamesAndAttrs, FS2} = get_attrs(AbsPath, Files, FileMod, FS1), ssh_xfer:xf_send_names(XF, ReqId, NamesAndAttrs), Handles = lists:keyreplace(Handle, 1, State0#state.handles, {Handle, directory, {RelPath,eof}}), State0#state{handles = Handles, file_state = FS2}; {error, Error} -> State1 = State0#state{file_state = FS1}, send_status({error, Error}, ReqId, State1) end. %%% get_attrs: get stat of each file and return get_attrs(RelPath, Files, FileMod, FS) -> get_attrs(RelPath, Files, FileMod, FS, []). get_attrs(_RelPath, [], _FileMod, FS, Acc) -> {lists:reverse(Acc), FS}; get_attrs(RelPath, [F | Rest], FileMod, FS0, Acc) -> Path = filename:absname(F, RelPath), ?dbg(true, "get_attrs fun: F=~p\n", [F]), case FileMod:read_link_info(Path, FS0) of {{ok, Info}, FS1} -> Attrs = ssh_sftp:info_to_attr(Info), get_attrs(RelPath, Rest, FileMod, FS1, [{F, Attrs} | Acc]); {{error, enoent}, FS1} -> get_attrs(RelPath, Rest, FileMod, FS1, Acc); {Error, FS1} -> {Error, FS1} end. close_our_file({_,Fd}, FileMod, FS0) -> {_Res, FS1} = FileMod:close(Fd, FS0), FS1. %%% stat: do the stat stat(Vsn, ReqId, Data, State, F) when Vsn =< 3-> <> = Data, stat(ReqId, binary_to_list(BPath), State, F); stat(Vsn, ReqId, Data, State, F) when Vsn >= 4-> <> = Data, stat(ReqId, binary_to_list(BPath), State, F). fstat(Vsn, ReqId, Data, State) when Vsn =< 3-> <> = Data, fstat(ReqId, Handle, State); fstat(Vsn, ReqId, Data, State) when Vsn >= 4-> <> = Data, fstat(ReqId, Handle, State). fstat(ReqId, BinHandle, State) -> case get_handle(State#state.handles, BinHandle) of {_Handle, _Type, {Path, _}} -> stat(ReqId, Path, State, read_file_info); _ -> ssh_xfer:xf_send_status(State#state.xf, ReqId, ?SSH_FX_INVALID_HANDLE), State end. stat(ReqId, RelPath, State0=#state{file_handler=FileMod, file_state=FS0}, F) -> AbsPath = relate_file_name(RelPath, State0), XF = State0#state.xf, ?dbg(false, "stat: AbsPath=~p\n", [AbsPath]), {Res, FS1} = FileMod:F(AbsPath, FS0), State1 = State0#state{file_state = FS1}, case Res of {ok, FileInfo} -> ssh_xfer:xf_send_attr(XF, ReqId, ssh_sftp:info_to_attr(FileInfo)), State1; {error, E} -> send_status({error, E}, ReqId, State1) end. decode_4_open_flag(create_new) -> [write]; decode_4_open_flag(create_truncate) -> [write]; decode_4_open_flag(truncate_existing) -> [write]; decode_4_open_flag(open_existing) -> [read,write]. decode_4_flags([OpenFlag | Flags]) -> decode_4_flags(Flags, decode_4_open_flag(OpenFlag)). decode_4_flags([], Flags) -> Flags; decode_4_flags([append_data|R], _Flags) -> decode_4_flags(R, [append]); decode_4_flags([append_data_atomic|R], _Flags) -> decode_4_flags(R, [append]); decode_4_flags([_|R], Flags) -> decode_4_flags(R, Flags). open(Vsn, ReqId, Data, State) when Vsn =< 3 -> <> = Data, Path = binary_to_list(BPath), %% ?dbg(true, "open: PFlags=~p\n", [PFlags]), Flags = ssh_xfer:decode_open_flags(Vsn, PFlags) -- [creat, excl, trunc], %% ?dbg(true, "open: F=~p\n", [F]), %% Flags = F -- [trunc], %% Flags = case lists:member(trunc, F) of %% true -> F -- [trunc]; %% _ -> F %% end, ?dbg(true, "open: Flags=~p\n", [Flags]), do_open(ReqId, State, Path, Flags); open(Vsn, ReqId, Data, State) when Vsn >= 4 -> <> = Data, Path = binary_to_list(BPath), Fl = ssh_xfer:decode_open_flags(Vsn, PFlags), ?dbg(true, "open: Fl=~p\n", [Fl]), Flags = decode_4_flags(Fl), ?dbg(true, "open: Flags=~p\n", [Flags]), do_open(ReqId, State, Path, Flags). do_open(ReqId, State0, Path, Flags) -> #state{file_handler = FileMod, file_state = FS0} = State0, XF = State0#state.xf, F = [raw, binary | Flags], %% case FileMod:is_dir(Path) of %% This is version 6 we still have 5 %% true -> %% ssh_xfer:xf_send_status(State#state.xf, ReqId, %% ?SSH_FX_FILE_IS_A_DIRECTORY); %% false -> {Res, FS1} = FileMod:open(Path, F, FS0), State1 = State0#state{file_state = FS1}, case Res of {ok, IoDevice} -> add_handle(State1, XF, ReqId, file, {Path,IoDevice}); {error, Error} -> ssh_xfer:xf_send_status(State1#state.xf, ReqId, ssh_xfer:encode_erlang_status(Error)), State1 %%end end. %%% relate given filenames to our CWD (needed? seems we get abs. path %%% most times) relate_file_name(F, State) when binary(F) -> relate_file_name(binary_to_list(F), State); relate_file_name(F, #state{cwd = CWD, root = Root}) -> F1 = filename:absname(F, CWD), Parts = fix_file_name(filename:split(F1), []), R = Root++filename:join(Parts), R. %%% fix file just a little: a/b/.. -> a and a/. -> a fix_file_name([".." | Rest], ["/"] = Acc) -> fix_file_name(Rest, Acc); fix_file_name([".." | Rest], [_Dir | Paths]) -> fix_file_name(Rest, Paths); fix_file_name(["." | Rest], Acc) -> fix_file_name(Rest, Acc); fix_file_name([A | Rest], Acc) -> fix_file_name(Rest, [A | Acc]); fix_file_name([], Acc) -> lists:reverse(Acc). read_file(ReqId, IoDevice, Offset, Len, State0 = #state{file_handler = FileMod, file_state = FS0}) -> {Res1, FS1} = FileMod:position(IoDevice, {bof, Offset}, FS0), case Res1 of {ok, _NewPos} -> {Res2, FS2} = FileMod:read(IoDevice, Len, FS1), State1 = State0#state{file_state = FS2}, case Res2 of {ok, Data} -> ssh_xfer:xf_send_data(State1#state.xf, ReqId, Data), State1; {error, Error} -> send_status({error, Error}, ReqId, State1); eof -> send_status(eof, ReqId, State1) end; {error, Error} -> State1 = State0#state{file_state = FS1}, send_status({error, Error}, ReqId, State1) end. write_file(ReqId, IoDevice, Offset, Data, State0 = #state{file_handler = FileMod, file_state = FS0}) -> {Res, FS1} = FileMod:position(IoDevice, {bof, Offset}, FS0), case Res of {ok, _NewPos} -> {Status, FS2} = FileMod:write(IoDevice, Data, FS1), State1 = State0#state{file_state = FS2}, send_status(Status, ReqId, State1); {error, Error} -> State1 = State0#state{file_state = FS1}, send_status({error, Error}, ReqId, State1) end. get_status(ok) -> ?SSH_FX_OK; get_status(eof) -> ?SSH_FX_EOF; get_status({error,Error}) -> ssh_xfer:encode_erlang_status(Error). send_status(Status, ReqId, State) -> ssh_xfer:xf_send_status(State#state.xf, ReqId, get_status(Status)), State. %% set_stat(<<>>, _Path, State) -> {ok, State}; set_stat(Attr, Path, State0 = #state{file_handler=FileMod, file_state=FS0}) -> {DecodedAttr, _Rest} = ssh_xfer:decode_ATTR((State0#state.xf)#ssh_xfer.vsn, Attr), ?dbg(true, "set_stat DecodedAttr=~p\n", [DecodedAttr]), Info = ssh_sftp:attr_to_info(DecodedAttr), {Res1, FS1} = FileMod:read_link_info(Path, FS0), case Res1 of {ok, OldInfo} -> NewInfo = set_file_info(Info, OldInfo), ?dbg(true, "set_stat Path=~p\nInfo=~p\nOldInfo=~p\nNewInfo=~p\n", [Path, Info, OldInfo, NewInfo]), {Res2, FS2} = FileMod:write_file_info(Path, NewInfo, FS1), State1 = State0#state{file_state = FS2}, {Res2, State1}; {error, Error} -> State1 = State0#state{file_state = FS1}, {{error, Error}, State1} end. set_file_info_sel(undefined, F) -> F; set_file_info_sel(F, _) -> F. set_file_info(#file_info{atime = Dst_atime, mtime = Dst_mtime, ctime = Dst_ctime, mode = Dst_mode, uid = Dst_uid, gid = Dst_gid}, #file_info{atime = Src_atime, mtime = Src_mtime, ctime = Src_ctime, mode = Src_mode, uid = Src_uid, gid = Src_gid}) -> #file_info{atime = set_file_info_sel(Dst_atime, Src_atime), mtime = set_file_info_sel(Dst_mtime, Src_mtime), ctime = set_file_info_sel(Dst_ctime, Src_ctime), mode = set_file_info_sel(Dst_mode, Src_mode), uid = set_file_info_sel(Dst_uid, Src_uid), gid = set_file_info_sel(Dst_gid, Src_gid)}. rename(Path, Path2, ReqId, State0) -> #state{file_handler = FileMod, file_state = FS0} = State0, {Status, FS1} = FileMod:rename(Path, Path2, FS0), State1 = State0#state{file_state = FS1}, send_status(Status, ReqId, State1).