diff options
Diffstat (limited to 'lib/ssh/src/ssh_xfer.erl')
-rw-r--r-- | lib/ssh/src/ssh_xfer.erl | 925 |
1 files changed, 925 insertions, 0 deletions
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl new file mode 100644 index 0000000000..a347a9c095 --- /dev/null +++ b/lib/ssh/src/ssh_xfer.erl @@ -0,0 +1,925 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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% +%% + +%% + +%%% Description: SFTP functions + +-module(ssh_xfer). + +-export([attach/2, connect/3]). +-export([open/6, opendir/3, readdir/3, close/3, read/5, write/5, + rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4, + stat/4, fstat/4, lstat/4, setstat/4, + readlink/3, fsetstat/4, symlink/4, + protocol_version_request/1, + xf_reply/2, + xf_send_reply/3, xf_send_names/3, xf_send_name/4, + xf_send_status/3, xf_send_status/4, xf_send_status/5, + xf_send_handle/3, xf_send_attr/3, xf_send_data/3, + encode_erlang_status/1, + decode_open_flags/2, encode_open_flags/1, + decode_ace_mask/1, decode_ext/1, + decode_ATTR/2, encode_ATTR/2]). + +-include("ssh.hrl"). +-include("ssh_xfer.hrl"). + +-import(lists, [foldl/3, reverse/1]). + +-define(is_set(F, Bits), + ((F) band (Bits)) == (F)). + +-define(XFER_PACKET_SIZE, 32768). +-define(XFER_WINDOW_SIZE, 4*?XFER_PACKET_SIZE). + +attach(CM, Opts) -> + open_xfer(CM, Opts). + +connect(Host, Port, Opts) -> + case ssh:connect(Host, Port, Opts) of + {ok, CM} -> open_xfer(CM, Opts); + Error -> Error + end. + +open_xfer(CM, Opts) -> + TMO = proplists:get_value(timeout, Opts, infinity), + case ssh_connection:session_channel(CM, ?XFER_WINDOW_SIZE, ?XFER_PACKET_SIZE, TMO) of + {ok, ChannelId} -> + {ok, ChannelId, CM}; + Error -> + Error + end. + +protocol_version_request(XF) -> + xf_request(XF, ?SSH_FXP_INIT, <<?UINT32(?SSH_SFTP_PROTOCOL_VERSION)>>). + +open(XF, ReqID, FileName, Access, Flags, Attrs) -> + Vsn = XF#ssh_xfer.vsn, + FileName1 = list_to_binary(FileName), + MBits = if Vsn >= 5 -> + M = encode_ace_mask(Access), + ?uint32(M); + true -> + (<<>>) + end, + F = encode_open_flags(Flags), + xf_request(XF,?SSH_FXP_OPEN, + [?uint32(ReqID), + ?binary(FileName1), + MBits, + ?uint32(F), + encode_ATTR(Vsn,Attrs)]). + +opendir(XF, ReqID, DirName) -> + xf_request(XF, ?SSH_FXP_OPENDIR, + [?uint32(ReqID), + ?string(DirName)]). + + +close(XF, ReqID, Handle) -> + xf_request(XF, ?SSH_FXP_CLOSE, + [?uint32(ReqID), + ?binary(Handle)]). + +read(XF, ReqID, Handle, Offset, Length) -> + xf_request(XF, ?SSH_FXP_READ, + [?uint32(ReqID), + ?binary(Handle), + ?uint64(Offset), + ?uint32(Length)]). + +readdir(XF, ReqID, Handle) -> + xf_request(XF, ?SSH_FXP_READDIR, + [?uint32(ReqID), + ?binary(Handle)]). + +write(XF,ReqID, Handle, Offset, Data) -> + Data1 = if + is_binary(Data) -> + Data; + is_list(Data) -> + list_to_binary(Data) + end, + xf_request(XF,?SSH_FXP_WRITE, + [?uint32(ReqID), + ?binary(Handle), + ?uint64(Offset), + ?binary(Data1)]). + +%% Remove a file +remove(XF, ReqID, File) -> + xf_request(XF, ?SSH_FXP_REMOVE, + [?uint32(ReqID), + ?string(File)]). + +%% Rename a file/directory +rename(XF, ReqID, Old, New, Flags) -> + Vsn = XF#ssh_xfer.vsn, + OldPath = list_to_binary(Old), + NewPath = list_to_binary(New), + FlagBits + = if Vsn >= 5 -> + F0 = encode_rename_flags(Flags), + ?uint32(F0); + true -> + (<<>>) + end, + xf_request(XF, ?SSH_FXP_RENAME, + [?uint32(ReqID), + ?binary(OldPath), + ?binary(NewPath), + FlagBits]). + + + +%% Create directory +mkdir(XF, ReqID, Path, Attrs) -> + Path1 = list_to_binary(Path), + xf_request(XF, ?SSH_FXP_MKDIR, + [?uint32(ReqID), + ?binary(Path1), + encode_ATTR(XF#ssh_xfer.vsn, Attrs)]). + +%% Remove a directory +rmdir(XF, ReqID, Dir) -> + Dir1 = list_to_binary(Dir), + xf_request(XF, ?SSH_FXP_RMDIR, + [?uint32(ReqID), + ?binary(Dir1)]). + +%% Stat file +stat(XF, ReqID, Path, Flags) -> + Path1 = list_to_binary(Path), + Vsn = XF#ssh_xfer.vsn, + AttrFlags = if Vsn >= 5 -> + F = encode_attr_flags(Vsn, Flags), + ?uint32(F); + true -> + [] + end, + xf_request(XF, ?SSH_FXP_STAT, + [?uint32(ReqID), + ?binary(Path1), + AttrFlags]). + + +%% Stat file - follow symbolic links +lstat(XF, ReqID, Path, Flags) -> + Path1 = list_to_binary(Path), + Vsn = XF#ssh_xfer.vsn, + AttrFlags = if Vsn >= 5 -> + F = encode_attr_flags(Vsn, Flags), + ?uint32(F); + true -> + [] + end, + xf_request(XF, ?SSH_FXP_LSTAT, + [?uint32(ReqID), + ?binary(Path1), + AttrFlags]). + +%% Stat open file +fstat(XF, ReqID, Handle, Flags) -> + Vsn = XF#ssh_xfer.vsn, + AttrFlags = if Vsn >= 5 -> + F = encode_attr_flags(Vsn, Flags), + ?uint32(F); + true -> + [] + end, + xf_request(XF, ?SSH_FXP_FSTAT, + [?uint32(ReqID), + ?binary(Handle), + AttrFlags]). + +%% Modify file attributes +setstat(XF, ReqID, Path, Attrs) -> + Path1 = list_to_binary(Path), + xf_request(XF, ?SSH_FXP_SETSTAT, + [?uint32(ReqID), + ?binary(Path1), + encode_ATTR(XF#ssh_xfer.vsn, Attrs)]). + + +%% Modify file attributes +fsetstat(XF, ReqID, Handle, Attrs) -> + xf_request(XF, ?SSH_FXP_FSETSTAT, + [?uint32(ReqID), + ?binary(Handle), + encode_ATTR(XF#ssh_xfer.vsn, Attrs)]). + +%% Read a symbolic link +readlink(XF, ReqID, Path) -> + Path1 = list_to_binary(Path), + xf_request(XF, ?SSH_FXP_READLINK, + [?uint32(ReqID), + ?binary(Path1)]). + + +%% Create a symbolic link +symlink(XF, ReqID, LinkPath, TargetPath) -> + LinkPath1 = list_to_binary(LinkPath), + TargetPath1 = list_to_binary(TargetPath), + xf_request(XF, ?SSH_FXP_SYMLINK, + [?uint32(ReqID), + ?binary(LinkPath1), + ?binary(TargetPath1)]). + +%% Convert a path into a 'canonical' form +realpath(XF, ReqID, Path) -> + Path1 = list_to_binary(Path), + xf_request(XF, ?SSH_FXP_REALPATH, + [?uint32(ReqID), + ?binary(Path1)]). + +extended(XF, ReqID, Request, Data) -> + xf_request(XF, ?SSH_FXP_EXTENDED, + [?uint32(ReqID), + ?string(Request), + ?binary(Data)]). + + +%% Send xfer request to connection manager +xf_request(XF, Op, Arg) -> + CM = XF#ssh_xfer.cm, + Channel = XF#ssh_xfer.channel, + Data = if + is_binary(Arg) -> + Arg; + is_list(Arg) -> + list_to_binary(Arg) + end, + Size = 1+size(Data), + ssh_connection:send(CM, Channel, <<?UINT32(Size), Op, Data/binary>>). + +xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) -> + Data = if + is_binary(Arg) -> + Arg; + is_list(Arg) -> + list_to_binary(Arg) + end, + Size = 1 + size(Data), + ssh_connection:send(CM, Channel, <<?UINT32(Size), Op, Data/binary>>). + +xf_send_name(XF, ReqId, Name, Attr) -> + xf_send_names(XF, ReqId, [{Name, Attr}]). + + +xf_send_handle(#ssh_xfer{cm = CM, channel = Channel}, + ReqId, Handle) -> + HLen = length(Handle), + Size = 1 + 4 + 4+HLen, + ToSend = [<<?UINT32(Size), ?SSH_FXP_HANDLE, ?UINT32(ReqId), ?UINT32(HLen)>>, + Handle], + ssh_connection:send(CM, Channel, ToSend). + +xf_send_names(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, + ReqId, NamesAndAttrs) -> + Count = length(NamesAndAttrs), + {Data, Len} = encode_names(Vsn, NamesAndAttrs), + Size = 1 + 4 + 4 + Len, + ToSend = [<<?UINT32(Size), ?SSH_FXP_NAME, ?UINT32(ReqId), ?UINT32(Count)>>, + Data], + %%?dbg(true, "xf_send_names: Size=~p size(ToSend)=~p\n", + %% [Size, size(list_to_binary(ToSend))]), + ssh_connection:send(CM, Channel, ToSend). + +xf_send_status(XF, ReqId, ErrorCode) -> + xf_send_status(XF, ReqId, ErrorCode, ""). + +xf_send_status(XF, ReqId, ErrorCode, ErrorMsg) -> + xf_send_status(XF, ReqId, ErrorCode, ErrorMsg, <<>>). + +xf_send_status(#ssh_xfer{cm = CM, channel = Channel}, + ReqId, ErrorCode, ErrorMsg, Data) -> + LangTag = "en", + ELen = length(ErrorMsg), + TLen = 2, %% length(LangTag), + Size = 1 + 4 + 4 + 4+ELen + 4+TLen + size(Data), + ToSend = [<<?UINT32(Size), ?SSH_FXP_STATUS, ?UINT32(ReqId), + ?UINT32(ErrorCode)>>, + <<?UINT32(ELen)>>, ErrorMsg, + <<?UINT32(TLen)>>, LangTag, + Data], + ssh_connection:send(CM, Channel, ToSend). + +xf_send_attr(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, ReqId, Attr) -> + EncAttr = encode_ATTR(Vsn, Attr), + ALen = size(EncAttr), + Size = 1 + 4 + ALen, + ToSend = [<<?UINT32(Size), ?SSH_FXP_ATTRS, ?UINT32(ReqId)>>, EncAttr], + ssh_connection:send(CM, Channel, ToSend). + +xf_send_data(#ssh_xfer{cm = CM, channel = Channel}, ReqId, Data) -> + DLen = size(Data), + Size = 1 + 4 + 4+DLen, + ToSend = [<<?UINT32(Size), ?SSH_FXP_DATA, ?UINT32(ReqId), ?UINT32(DLen)>>, + Data], + ssh_connection:send(CM, Channel, ToSend). + +xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status), + ?UINT32(ELen), Err:ELen/binary, + ?UINT32(LLen), Lang:LLen/binary, + Reply/binary >> ) -> + Stat = decode_status(Status), + {status, ReqID, {Stat,binary_to_list(Err),binary_to_list(Lang), + Reply}}; +xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status)>> ) -> + Stat = decode_status(Status), + {status, ReqID, {Stat,"","",<<>>}}; +xf_reply(_XF, <<?SSH_FXP_HANDLE, ?UINT32(ReqID), + ?UINT32(HLen), Handle:HLen/binary>>) -> + {handle, ReqID, Handle}; +xf_reply(_XF, <<?SSH_FXP_DATA, ?UINT32(ReqID), + ?UINT32(DLen), Data:DLen/binary>>) -> + {data, ReqID, Data}; +xf_reply(XF, <<?SSH_FXP_NAME, ?UINT32(ReqID), + ?UINT32(Count), AData/binary>>) -> + %%?dbg(true, "xf_reply ?SSH_FXP_NAME: AData=~p\n", [AData]), + {name, ReqID, decode_names(XF#ssh_xfer.vsn, Count, AData)}; +xf_reply(XF, <<?SSH_FXP_ATTRS, ?UINT32(ReqID), + AData/binary>>) -> + {A, _} = decode_ATTR(XF#ssh_xfer.vsn, AData), + {attrs, ReqID, A}; +xf_reply(_XF, <<?SSH_FXP_EXTENDED_REPLY, ?UINT32(ReqID), + RData>>) -> + {extended_reply, ReqID, RData}. + + + +decode_status(Status) -> + case Status of + ?SSH_FX_OK -> ok; + ?SSH_FX_EOF -> eof; + ?SSH_FX_NO_SUCH_FILE -> no_such_file; + ?SSH_FX_PERMISSION_DENIED -> permission_denied; + ?SSH_FX_FAILURE -> failure; + ?SSH_FX_BAD_MESSAGE -> bad_message; + ?SSH_FX_NO_CONNECTION -> no_connection; + ?SSH_FX_CONNECTION_LOST -> connection_lost; + ?SSH_FX_OP_UNSUPPORTED -> op_unsupported; + ?SSH_FX_INVALID_HANDLE -> invalid_handle; + ?SSH_FX_NO_SUCH_PATH -> no_such_path; + ?SSH_FX_FILE_ALREADY_EXISTS -> file_already_exists; + ?SSH_FX_WRITE_PROTECT -> write_protect; + ?SSH_FX_NO_MEDIA -> no_media; + ?SSH_FX_NO_SPACE_ON_FILESYSTEM -> no_space_on_filesystem; + ?SSH_FX_QUOTA_EXCEEDED -> quota_exceeded; + ?SSH_FX_UNKNOWN_PRINCIPLE -> unknown_principle; + ?SSH_FX_LOCK_CONFlICT -> lock_conflict; + ?SSH_FX_NOT_A_DIRECTORY -> not_a_directory; + _ -> {error,Status} + end. + +encode_erlang_status(Status) -> + case Status of + ok -> ?SSH_FX_OK; + eof -> ?SSH_FX_EOF; + enoent -> ?SSH_FX_NO_SUCH_FILE; + eacces -> ?SSH_FX_PERMISSION_DENIED; + _ -> ?SSH_FX_FAILURE + end. + +decode_ext(<<?UINT32(NameLen), Name:NameLen/binary, + ?UINT32(DataLen), Data:DataLen/binary, + Tail/binary>>) -> + [{binary_to_list(Name), binary_to_list(Data)} + | decode_ext(Tail)]; +decode_ext(<<>>) -> + []. + +%% +%% Encode rename flags +%% +encode_rename_flags(Flags) -> + encode_bits( + fun(overwrite) -> ?SSH_FXP_RENAME_OVERWRITE; + (atomic) -> ?SSH_FXP_RENAME_ATOMIC; + (native) -> ?SSH_FXP_RENAME_NATIVE + end, Flags). + +%% decode_rename_flags(F) -> +%% decode_bits(F, +%% [{?SSH_FXP_RENAME_OVERWRITE, overwrite}, +%% {?SSH_FXP_RENAME_ATOMIC, atomic}, +%% {?SSH_FXP_RENAME_NATIVE, native}]). + + +encode_open_flags(Flags) -> + encode_bits( + fun (read) -> ?SSH_FXF_READ; + (write) -> ?SSH_FXF_WRITE; + (append) -> ?SSH_FXF_APPEND; + (creat) -> ?SSH_FXF_CREAT; + (trunc) -> ?SSH_FXF_TRUNC; + (excl) -> ?SSH_FXF_EXCL; + (create_new) -> ?SSH_FXF_CREATE_NEW; + (create_truncate) -> ?SSH_FXF_CREATE_TRUNCATE; + (open_existing) -> ?SSH_FXF_OPEN_EXISTING; + (open_or_create) -> ?SSH_FXF_OPEN_OR_CREATE; + (truncate_existing) -> ?SSH_FXF_TRUNCATE_EXISTING; + (append_data) -> ?SSH_FXF_ACCESS_APPEND_DATA; + (append_data_atomic) -> ?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC; + (text_mode) -> ?SSH_FXF_ACCESS_TEXT_MODE; + (read_lock) -> ?SSH_FXF_ACCESS_READ_LOCK; + (write_lock) -> ?SSH_FXF_ACCESS_WRITE_LOCK; + (delete_lock) -> ?SSH_FXF_ACCESS_DELETE_LOCK + end, Flags). + +encode_ace_mask(Access) -> + encode_bits( + fun(read_data) -> ?ACE4_READ_DATA; + (list_directory) -> ?ACE4_LIST_DIRECTORY; + (write_data) -> ?ACE4_WRITE_DATA; + (add_file) -> ?ACE4_ADD_FILE; + (append_data) -> ?ACE4_APPEND_DATA; + (add_subdirectory) -> ?ACE4_ADD_SUBDIRECTORY; + (read_named_attrs) -> ?ACE4_READ_NAMED_ATTRS; + (write_named_attrs) -> ?ACE4_WRITE_NAMED_ATTRS; + (execute) -> ?ACE4_EXECUTE; + (delete_child) -> ?ACE4_DELETE_CHILD; + (read_attributes) -> ?ACE4_READ_ATTRIBUTES; + (write_attributes) -> ?ACE4_WRITE_ATTRIBUTES; + (delete) -> ?ACE4_DELETE; + (read_acl) -> ?ACE4_READ_ACL; + (write_acl) -> ?ACE4_WRITE_ACL; + (write_owner) -> ?ACE4_WRITE_OWNER; + (synchronize) -> ?ACE4_SYNCHRONIZE + end, Access). + +decode_ace_mask(F) -> + decode_bits(F, + [ + {?ACE4_READ_DATA, read_data}, + {?ACE4_LIST_DIRECTORY, list_directory}, + {?ACE4_WRITE_DATA, write_data}, + {?ACE4_ADD_FILE, add_file}, + {?ACE4_APPEND_DATA, append_data}, + {?ACE4_ADD_SUBDIRECTORY, add_subdirectory}, + {?ACE4_READ_NAMED_ATTRS, read_named_attrs}, + {?ACE4_WRITE_NAMED_ATTRS, write_named_attrs}, + {?ACE4_EXECUTE, execute}, + {?ACE4_DELETE_CHILD, delete_child}, + {?ACE4_READ_ATTRIBUTES, read_attributes}, + {?ACE4_WRITE_ATTRIBUTES, write_attributes}, + {?ACE4_DELETE, delete}, + {?ACE4_READ_ACL, read_acl}, + {?ACE4_WRITE_ACL, write_acl}, + {?ACE4_WRITE_OWNER, write_owner}, + {?ACE4_SYNCHRONIZE, synchronize} + ]). + +decode_open_flags(Vsn, F) when Vsn =< 3 -> + decode_bits(F, + [ + {?SSH_FXF_READ, read}, + {?SSH_FXF_WRITE, write}, + {?SSH_FXF_APPEND, append}, + {?SSH_FXF_CREAT, creat}, + {?SSH_FXF_TRUNC, trunc}, + {?SSH_FXF_EXCL, excl} + ]); +decode_open_flags(Vsn, F) when Vsn >= 4 -> + R = decode_bits(F, + [ + {?SSH_FXF_ACCESS_APPEND_DATA, append_data}, + {?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC, append_data_atomic}, + {?SSH_FXF_ACCESS_TEXT_MODE, text_mode}, + {?SSH_FXF_ACCESS_READ_LOCK, read_lock}, + {?SSH_FXF_ACCESS_WRITE_LOCK, write_lock}, + {?SSH_FXF_ACCESS_DELETE_LOCK, delete_lock} + ]), + AD = case F band ?SSH_FXF_ACCESS_DISPOSITION of + ?SSH_FXF_CREATE_NEW -> create_new; + ?SSH_FXF_CREATE_TRUNCATE -> create_truncate; + ?SSH_FXF_OPEN_EXISTING -> open_existing; + ?SSH_FXF_OPEN_OR_CREATE -> open_or_create; + ?SSH_FXF_TRUNCATE_EXISTING -> truncate_existing + end, + [AD | R]. + +encode_ace_type(Type) -> + case Type of + access_allowed -> ?ACE4_ACCESS_ALLOWED_ACE_TYPE; + access_denied -> ?ACE4_ACCESS_DENIED_ACE_TYPE; + system_audit -> ?ACE4_SYSTEM_AUDIT_ACE_TYPE; + system_alarm -> ?ACE4_SYSTEM_ALARM_ACE_TYPE + end. + +decode_ace_type(F) -> + case F of + ?ACE4_ACCESS_ALLOWED_ACE_TYPE -> access_allowed; + ?ACE4_ACCESS_DENIED_ACE_TYPE -> access_denied; + ?ACE4_SYSTEM_AUDIT_ACE_TYPE -> system_audit; + ?ACE4_SYSTEM_ALARM_ACE_TYPE -> system_alarm + end. + +encode_ace_flag(Flag) -> + encode_bits( + fun(file_inherit) -> ?ACE4_FILE_INHERIT_ACE; + (directory_inherit) -> ?ACE4_DIRECTORY_INHERIT_ACE; + (no_propagte_inherit) -> ?ACE4_NO_PROPAGATE_INHERIT_ACE; + (inherit_only) -> ?ACE4_INHERIT_ONLY_ACE; + (successful_access) -> ?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG; + (failed_access) -> ?ACE4_FAILED_ACCESS_ACE_FLAG; + (identifier_group) -> ?ACE4_IDENTIFIER_GROUP + end, Flag). + +decode_ace_flag(F) -> + decode_bits(F, + [ + {?ACE4_FILE_INHERIT_ACE, file_inherit}, + {?ACE4_DIRECTORY_INHERIT_ACE, directory_inherit}, + {?ACE4_NO_PROPAGATE_INHERIT_ACE, no_propagte_inherit}, + {?ACE4_INHERIT_ONLY_ACE, inherit_only}, + {?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, successful_access}, + {?ACE4_FAILED_ACCESS_ACE_FLAG, failed_access}, + {?ACE4_IDENTIFIER_GROUP, identifier_group} + ]). + +encode_attr_flags(Vsn, all) -> + encode_attr_flags(Vsn, + [size, uidgid, permissions, + acmodtime, accesstime, createtime, + modifytime, acl, ownergroup, subsecond_times, + bits, extended]); +encode_attr_flags(Vsn, Flags) -> + encode_bits( + fun(size) -> ?SSH_FILEXFER_ATTR_SIZE; + (uidgid) when Vsn =<3 -> ?SSH_FILEXFER_ATTR_UIDGID; + (permissions) -> ?SSH_FILEXFER_ATTR_PERMISSIONS; + (acmodtime) when Vsn =< 3 -> ?SSH_FILEXFER_ATTR_ACMODTIME; + (accesstime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACCESSTIME; + (createtime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_CREATETIME; + (modifytime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_MODIFYTIME; + (acl) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACL; + (ownergroup) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_OWNERGROUP; + (subsecond_times) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES; + (bits) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_BITS; + (extended) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_EXTENDED; + (_) -> 0 + end, Flags). + +encode_file_type(Type) -> + %%?dbg(true, "encode_file_type(~p)\n", [Type]), + case Type of + regular -> ?SSH_FILEXFER_TYPE_REGULAR; + directory -> ?SSH_FILEXFER_TYPE_DIRECTORY; + symlink -> ?SSH_FILEXFER_TYPE_SYMLINK; + special -> ?SSH_FILEXFER_TYPE_SPECIAL; + unknown -> ?SSH_FILEXFER_TYPE_UNKNOWN; + other -> ?SSH_FILEXFER_TYPE_UNKNOWN; + socket -> ?SSH_FILEXFER_TYPE_SOCKET; + char_device -> ?SSH_FILEXFER_TYPE_CHAR_DEVICE; + block_device -> ?SSH_FILEXFER_TYPE_BLOCK_DEVICE; + fifo -> ?SSH_FILEXFER_TYPE_FIFO; + undefined -> ?SSH_FILEXFER_TYPE_UNKNOWN + end. + +decode_file_type(Type) -> + case Type of + ?SSH_FILEXFER_TYPE_REGULAR -> regular; + ?SSH_FILEXFER_TYPE_DIRECTORY -> directory; + ?SSH_FILEXFER_TYPE_SYMLINK -> symlink; + ?SSH_FILEXFER_TYPE_SPECIAL -> special; + ?SSH_FILEXFER_TYPE_UNKNOWN -> other; % unknown + ?SSH_FILEXFER_TYPE_SOCKET -> socket; + ?SSH_FILEXFER_TYPE_CHAR_DEVICE -> char_device; + ?SSH_FILEXFER_TYPE_BLOCK_DEVICE -> block_device; + ?SSH_FILEXFER_TYPE_FIFO -> fifo + end. + +encode_attrib_bits(Bits) -> + encode_bits( + fun(readonly) -> ?SSH_FILEXFER_ATTR_FLAGS_READONLY; + (system) -> ?SSH_FILEXFER_ATTR_FLAGS_SYSTEM; + (hidden) -> ?SSH_FILEXFER_ATTR_FLAGS_HIDDEN; + (case_insensitive) -> ?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE; + (arcive) -> ?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE; + (encrypted) -> ?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED; + (compressed) -> ?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED; + (sparse) -> ?SSH_FILEXFER_ATTR_FLAGS_SPARSE; + (append_only) -> ?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY; + (immutable) -> ?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE; + (sync) -> ?SSH_FILEXFER_ATTR_FLAGS_SYNC + end, Bits). + +decode_attrib_bits(F) -> + decode_bits(F, + [{?SSH_FILEXFER_ATTR_FLAGS_READONLY, readonly}, + {?SSH_FILEXFER_ATTR_FLAGS_SYSTEM, system}, + {?SSH_FILEXFER_ATTR_FLAGS_HIDDEN, hidden}, + {?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE, case_insensitive}, + {?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE, arcive}, + {?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED, encrypted}, + {?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED, compressed}, + {?SSH_FILEXFER_ATTR_FLAGS_SPARSE, sparse}, + {?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY, append_only}, + {?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE, immutable}, + {?SSH_FILEXFER_ATTR_FLAGS_SYNC, sync}]). + + +%% +%% Encode file attributes +%% +encode_ATTR(Vsn, A) -> + {Flags,As} = + encode_As(Vsn, + [{size, A#ssh_xfer_attr.size}, + {ownergroup, A#ssh_xfer_attr.owner}, + {ownergroup, A#ssh_xfer_attr.group}, + {permissions, A#ssh_xfer_attr.permissions}, + {acmodtime, A#ssh_xfer_attr.atime}, + {acmodtime, A#ssh_xfer_attr.mtime}, + {accesstime, A#ssh_xfer_attr.atime}, + {subsecond_times, A#ssh_xfer_attr.atime_nseconds}, + {createtime, A#ssh_xfer_attr.createtime}, + {subsecond_times, A#ssh_xfer_attr.createtime_nseconds}, + {modifytime, A#ssh_xfer_attr.mtime}, + {subsecond_times, A#ssh_xfer_attr.mtime_nseconds}, + {acl, A#ssh_xfer_attr.acl}, + {bits, A#ssh_xfer_attr.attrib_bits}, + {extended, A#ssh_xfer_attr.extensions}], + 0, []), + Type = encode_file_type(A#ssh_xfer_attr.type), + %%?dbg(true, "encode_ATTR: Vsn=~p A=~p As=~p Flags=~p Type=~p", + %% [Vsn, A, As, Flags, Type]), + Result = list_to_binary([?uint32(Flags), + if Vsn >= 5 -> + ?byte(Type); + true -> + (<<>>) + end, As]), + %% ?dbg(true, " Result=~p\n", [Result]), + Result. + + +encode_As(Vsn, [{_AName, undefined}|As], Flags, Acc) -> + encode_As(Vsn, As, Flags, Acc); +encode_As(Vsn, [{AName, X}|As], Flags, Acc) -> + case AName of + size -> + encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_SIZE, + [?uint64(X) | Acc]); + ownergroup when Vsn=<4 -> + encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_UIDGID, + [?uint32(X) | Acc]); + ownergroup when Vsn>=5 -> + X1 = list_to_binary(integer_to_list(X)), % TODO: check owner and group + encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_OWNERGROUP, + [?binary(X1) | Acc]); + permissions -> + encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_PERMISSIONS, + [?uint32(X) | Acc]); + acmodtime when Vsn=<3 -> + encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_ACMODTIME, + [?uint32(X) | Acc]); + accesstime when Vsn>=5 -> + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACCESSTIME, + [?uint64(X) | Acc]); + createtime when Vsn>=5-> + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_CREATETIME, + [?uint64(X) | Acc]); + modifytime when Vsn>=5 -> + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_MODIFYTIME, + [?uint64(X) | Acc]); + subsecond_times when Vsn>=5 -> + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES, + [?uint64(X) | Acc]); + acl when Vsn >=5 -> + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACL, + [encode_acl(X) | Acc]); + bits when Vsn>=5 -> + F = encode_attrib_bits(X), + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_BITS, + [?uint32(F) | Acc]); + extended -> + encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_EXTENDED, + [encode_extensions(X) | Acc]); + _ -> + encode_As(Vsn, As, Flags, Acc) + end; +encode_As(_Vsn, [], Flags, Acc) -> + {Flags, reverse(Acc)}. + + +decode_ATTR(Vsn, <<?UINT32(Flags), Tail/binary>>) -> + %%?dbg(true, "decode_ATTR: Vsn=~p Flags=~p Tail=~p\n", [Vsn, Flags, Tail]), + {Type,Tail2} = + if Vsn =< 3 -> + {?SSH_FILEXFER_TYPE_UNKNOWN, Tail}; + Vsn >= 5 -> + <<?BYTE(T), TL/binary>> = Tail, + {T, TL} + end, + decode_As(Vsn, + [{size, #ssh_xfer_attr.size}, + {ownergroup, #ssh_xfer_attr.owner}, + {ownergroup, #ssh_xfer_attr.group}, + {permissions, #ssh_xfer_attr.permissions}, + {acmodtime, #ssh_xfer_attr.atime}, + {acmodtime, #ssh_xfer_attr.mtime}, + {accesstime, #ssh_xfer_attr.atime}, + {subsecond_times, #ssh_xfer_attr.atime_nseconds}, + {createtime, #ssh_xfer_attr.createtime}, + {subsecond_times, #ssh_xfer_attr.createtime_nseconds}, + {modifytime, #ssh_xfer_attr.mtime}, + {subsecond_times, #ssh_xfer_attr.mtime_nseconds}, + {acl, #ssh_xfer_attr.acl}, + {bits, #ssh_xfer_attr.attrib_bits}, + {extended, #ssh_xfer_attr.extensions}], + #ssh_xfer_attr { type = decode_file_type(Type) }, + Flags, + Tail2). + +decode_As(Vsn, [{AName, AField}|As], R, Flags, Tail) -> + %%?dbg(false, "decode_As: Vsn=~p AName=~p AField=~p Flags=~p Tail=~p\n", [Vsn, AName, AField, Flags, Tail]), + case AName of + size when ?is_set(?SSH_FILEXFER_ATTR_SIZE, Flags) -> + <<?UINT64(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + ownergroup when ?is_set(?SSH_FILEXFER_ATTR_UIDGID, Flags),Vsn=<3 -> + <<?UINT32(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + ownergroup when ?is_set(?SSH_FILEXFER_ATTR_OWNERGROUP, Flags),Vsn>=5 -> + <<?UINT32(Len), Bin:Len/binary, Tail2/binary>> = Tail, + X = binary_to_list(Bin), + %%?dbg(true, "ownergroup X=~p\n", [X]), + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + + permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn>=5-> + <<?UINT32(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + + permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn=<3-> + <<?UINT32(X), Tail2/binary>> = Tail, + R1 = setelement(AField, R, X), + Type = case X band ?S_IFMT of + ?S_IFDIR -> directory; + ?S_IFCHR -> char_device; + ?S_IFBLK -> block_device; + ?S_IFIFO -> fifi; + ?S_IFREG -> regular; + ?S_IFSOCK -> socket; + ?S_IFLNK -> symlink; + _ -> unknown + end, + decode_As(Vsn, As, R1#ssh_xfer_attr { type=Type}, Flags, Tail2); + + acmodtime when ?is_set(?SSH_FILEXFER_ATTR_ACMODTIME,Flags),Vsn=<3 -> + <<?UINT32(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + accesstime when ?is_set(?SSH_FILEXFER_ATTR_ACCESSTIME,Flags),Vsn>=5 -> + <<?UINT64(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + modifytime when ?is_set(?SSH_FILEXFER_ATTR_MODIFYTIME,Flags),Vsn>=5 -> + <<?UINT64(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + createtime when ?is_set(?SSH_FILEXFER_ATTR_CREATETIME,Flags),Vsn>=5 -> + <<?UINT64(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + subsecond_times when ?is_set(?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,Flags),Vsn>=5 -> + <<?UINT32(X), Tail2/binary>> = Tail, + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + acl when ?is_set(?SSH_FILEXFER_ATTR_ACL, Flags), Vsn>=5 -> + {X,Tail2} = decode_acl(Tail), + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + bits when ?is_set(?SSH_FILEXFER_ATTR_BITS, Flags), Vsn >=5 -> + <<?UINT32(Y), Tail2/binary>> = Tail, + X = decode_attrib_bits(Y), + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + extended when ?is_set(?SSH_FILEXFER_ATTR_EXTENDED, Flags) -> + {X,Tail2} = decode_extended(Tail), + decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); + _ -> + decode_As(Vsn, As, R, Flags, Tail) + end; +decode_As(_Vsn, [], R, _, Tail) -> + {R, Tail}. + + + + +decode_names(_Vsn, 0, _Data) -> + []; +decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary, + ?UINT32(LLen), _LongName:LLen/binary, + Tail/binary>>) when Vsn =< 3 -> + Name = binary_to_list(FileName), + %%?dbg(true, "decode_names: ~p\n", [Name]), + {A, Tail2} = decode_ATTR(Vsn, Tail), + [{Name, A} | decode_names(Vsn, I-1, Tail2)]; +decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary, + Tail/binary>>) when Vsn >= 4 -> + Name = binary_to_list(FileName), + %%?dbg(true, "decode_names: ~p\n", [Name]), + {A, Tail2} = decode_ATTR(Vsn, Tail), + [{Name, A} | decode_names(Vsn, I-1, Tail2)]. + +encode_names(Vsn, NamesAndAttrs) -> + lists:mapfoldl(fun(N, L) -> encode_name(Vsn, N, L) end, 0, NamesAndAttrs). + +encode_name(Vsn, {Name,Attr}, Len) when Vsn =< 3 -> + NLen = length(Name), + %%?dbg(true, "encode_name: Vsn=~p Name=~p Attr=~p\n", + %% [Vsn, Name, Attr]), + EncAttr = encode_ATTR(Vsn, Attr), + ALen = size(EncAttr), + NewLen = Len + NLen*2 + 4 + 4 + ALen, + {[<<?UINT32(NLen)>>, Name, <<?UINT32(NLen)>>, Name, EncAttr], NewLen}; +encode_name(Vsn, {Name,Attr}, Len) when Vsn >= 4 -> + NLen = length(Name), + EncAttr = encode_ATTR(Vsn, Attr), + ALen = size(EncAttr), + {[<<?UINT32(NLen)>>, Name, EncAttr], + Len + 4 + NLen + ALen}. + +encode_acl(ACLList) -> + Count = length(ACLList), + [?uint32(Count) | encode_acl_items(ACLList)]. + +encode_acl_items([ACE|As]) -> + Type = encode_ace_type(ACE#ssh_xfer_ace.type), + Flag = encode_ace_flag(ACE#ssh_xfer_ace.flag), + Mask = encode_ace_mask(ACE#ssh_xfer_ace.mask), + Who = list_to_binary(ACE#ssh_xfer_ace.who), + [?uint32(Type), ?uint32(Flag), ?uint32(Mask), + ?binary(Who) | encode_acl_items(As)]; +encode_acl_items([]) -> + []. + + +decode_acl(<<?UINT32(Count), Tail/binary>>) -> + decode_acl_items(Count, Tail, []). + +decode_acl_items(0, Tail, Acc) -> + {reverse(Acc), Tail}; +decode_acl_items(I, <<?UINT32(Type), + ?UINT32(Flag), + ?UINT32(Mask), + ?UINT32(WLen), BWho:WLen/binary, + Tail/binary>>, Acc) -> + decode_acl_items(I-1, Tail, + [#ssh_xfer_ace { type = decode_ace_type(Type), + flag = decode_ace_flag(Flag), + mask = decode_ace_mask(Mask), + who = binary_to_list(BWho)} | Acc]). + +encode_extensions(Exts) -> + Count = length(Exts), + [?uint32(Count) | encode_ext(Exts)]. + +encode_ext([{Type, Data} | Exts]) -> + [?string(Type), ?string(Data) | encode_ext(Exts)]; +encode_ext([]) -> + []. + + +decode_extended(<<?UINT32(Count), Tail/binary>>) -> + decode_ext(Count, Tail, []). + +decode_ext(0, Tail, Acc) -> + {reverse(Acc), Tail}; +decode_ext(I, <<?UINT32(TLen), Type:TLen/binary, + ?UINT32(DLen), Data:DLen/binary, + Tail/binary>>, Acc) -> + decode_ext(I-1, Tail, [{binary_to_list(Type), Data}|Acc]). + + + +%% Encode bit encoded flags +encode_bits(Fun, BitNames) -> + encode_bits(Fun, 0, BitNames). + +encode_bits(Fun, F, [Bit|BitNames]) -> + encode_bits(Fun, Fun(Bit) bor F, BitNames); +encode_bits(_Fun, F, []) -> + F. + +%% Decode bit encoded flags +decode_bits(F, [{Bit,BitName}|Bits]) -> + if F band Bit == Bit -> + [BitName | decode_bits(F, Bits)]; + true -> + decode_bits(F, Bits) + end; +decode_bits(_F, []) -> + []. |