1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2005-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 23%%% Description: SFTP functions 24 25-module(ssh_xfer). 26 27-export([attach/2, attach/3, connect/3, connect/4, connect/5]). 28-export([open/6, opendir/3, readdir/3, close/3, read/5, write/5, 29 rename/5, remove/3, mkdir/4, rmdir/3, realpath/3, extended/4, 30 stat/4, fstat/4, lstat/4, setstat/4, 31 readlink/3, fsetstat/4, symlink/4, 32 protocol_version_request/2, 33 xf_reply/2, 34 xf_send_reply/3, xf_send_names/3, xf_send_name/4, 35 xf_send_status/3, xf_send_status/4, xf_send_status/5, 36 xf_send_handle/3, xf_send_attr/3, xf_send_data/3, 37 encode_erlang_status/1, 38 decode_open_flags/2, encode_open_flags/1, 39 decode_ace_mask/1, decode_ext/1, 40 decode_ATTR/2, encode_ATTR/2]). 41 42-include("ssh.hrl"). 43-include("ssh_xfer.hrl"). 44 45-import(lists, [foldl/3, reverse/1]). 46 47-define(is_set(F, Bits), 48 ((F) band (Bits)) == (F)). 49 50-define(XFER_PACKET_SIZE, 65536). 51-define(XFER_WINDOW_SIZE, 20*?XFER_PACKET_SIZE). 52 53attach(CM, Opts) -> 54 open_xfer(CM, Opts, []). 55 56attach(CM, Opts, ChanOpts) -> 57 open_xfer(CM, Opts, ChanOpts). 58 59 60connect(Host, Port, Opts) -> 61 case ssh:connect(Host, Port, Opts) of 62 {ok, CM} -> open_xfer(CM, Opts, []); 63 Error -> Error 64 end. 65 66connect(Host, Port, Opts, Timeout) -> 67 connect(Host, Port, Opts, [], Timeout). 68 69connect(Host, Port, Opts, ChanOpts, Timeout) -> 70 case ssh:connect(Host, Port, Opts, Timeout) of 71 {ok, CM} -> open_xfer(CM, [{timeout, Timeout}|Opts], ChanOpts); 72 {error, Timeout} -> {error, timeout}; 73 Error -> Error 74 end. 75 76 77open_xfer(CM, Opts, ChanOpts) -> 78 TMO = proplists:get_value(timeout, Opts, infinity), 79 WindowSize = proplists:get_value(window_size, ChanOpts, ?XFER_WINDOW_SIZE), 80 PacketSize = proplists:get_value(packet_size, ChanOpts, ?XFER_PACKET_SIZE), 81 case ssh_connection:session_channel(CM, WindowSize, PacketSize, TMO) of 82 {ok, ChannelId} -> 83 {ok, ChannelId, CM}; 84 Error -> 85 Error 86 end. 87 88protocol_version_request(XF, Version) -> 89 xf_request(XF, ?SSH_FXP_INIT, <<?UINT32(Version)>>). 90 91open(XF, ReqID, FileName, Access, Flags, Attrs) -> 92 Vsn = XF#ssh_xfer.vsn, 93 MBits = if Vsn >= 5 -> 94 M = encode_ace_mask(Access), 95 ?uint32(M); 96 true -> 97 (<<>>) 98 end, 99 F = encode_open_flags(Flags), 100 xf_request(XF,?SSH_FXP_OPEN, 101 [?uint32(ReqID), 102 ?string_utf8(FileName), 103 MBits, 104 ?uint32(F), 105 encode_ATTR(Vsn,Attrs)]). 106 107opendir(XF, ReqID, DirName) -> 108 xf_request(XF, ?SSH_FXP_OPENDIR, 109 [?uint32(ReqID), 110 ?string_utf8(DirName)]). 111 112 113close(XF, ReqID, Handle) -> 114 xf_request(XF, ?SSH_FXP_CLOSE, 115 [?uint32(ReqID), 116 ?binary(Handle)]). 117 118read(XF, ReqID, Handle, Offset, Length) -> 119 xf_request(XF, ?SSH_FXP_READ, 120 [?uint32(ReqID), 121 ?binary(Handle), 122 ?uint64(Offset), 123 ?uint32(Length)]). 124 125readdir(XF, ReqID, Handle) -> 126 xf_request(XF, ?SSH_FXP_READDIR, 127 [?uint32(ReqID), 128 ?binary(Handle)]). 129 130write(XF,ReqID, Handle, Offset, Data) -> 131 Data1 = if 132 is_binary(Data) -> 133 Data; 134 is_list(Data) -> 135 unicode:characters_to_binary(Data) 136 end, 137 xf_request(XF,?SSH_FXP_WRITE, 138 [?uint32(ReqID), 139 ?binary(Handle), 140 ?uint64(Offset), 141 ?binary(Data1)]). 142 143%% Remove a file 144remove(XF, ReqID, File) -> 145 xf_request(XF, ?SSH_FXP_REMOVE, 146 [?uint32(ReqID), 147 ?string_utf8(File)]). 148 149%% Rename a file/directory 150rename(XF, ReqID, OldPath, NewPath, Flags) -> 151 Vsn = XF#ssh_xfer.vsn, 152 FlagBits 153 = if Vsn >= 5 -> 154 F0 = encode_rename_flags(Flags), 155 ?uint32(F0); 156 true -> 157 (<<>>) 158 end, 159 xf_request(XF, ?SSH_FXP_RENAME, 160 [?uint32(ReqID), 161 ?string_utf8(OldPath), 162 ?string_utf8(NewPath), 163 FlagBits]). 164 165 166 167%% Create directory 168mkdir(XF, ReqID, Path, Attrs) -> 169 xf_request(XF, ?SSH_FXP_MKDIR, 170 [?uint32(ReqID), 171 ?string_utf8(Path), 172 encode_ATTR(XF#ssh_xfer.vsn, Attrs)]). 173 174%% Remove a directory 175rmdir(XF, ReqID, Dir) -> 176 xf_request(XF, ?SSH_FXP_RMDIR, 177 [?uint32(ReqID), 178 ?string_utf8(Dir)]). 179 180%% Stat file 181stat(XF, ReqID, Path, Flags) -> 182 Vsn = XF#ssh_xfer.vsn, 183 AttrFlags = if Vsn >= 5 -> 184 F = encode_attr_flags(Vsn, Flags), 185 ?uint32(F); 186 true -> 187 [] 188 end, 189 xf_request(XF, ?SSH_FXP_STAT, 190 [?uint32(ReqID), 191 ?string_utf8(Path), 192 AttrFlags]). 193 194 195%% Stat file - follow symbolic links 196lstat(XF, ReqID, Path, Flags) -> 197 Vsn = XF#ssh_xfer.vsn, 198 AttrFlags = if Vsn >= 5 -> 199 F = encode_attr_flags(Vsn, Flags), 200 ?uint32(F); 201 true -> 202 [] 203 end, 204 xf_request(XF, ?SSH_FXP_LSTAT, 205 [?uint32(ReqID), 206 ?string_utf8(Path), 207 AttrFlags]). 208 209%% Stat open file 210fstat(XF, ReqID, Handle, Flags) -> 211 Vsn = XF#ssh_xfer.vsn, 212 AttrFlags = if Vsn >= 5 -> 213 F = encode_attr_flags(Vsn, Flags), 214 ?uint32(F); 215 true -> 216 [] 217 end, 218 xf_request(XF, ?SSH_FXP_FSTAT, 219 [?uint32(ReqID), 220 ?binary(Handle), 221 AttrFlags]). 222 223%% Modify file attributes 224setstat(XF, ReqID, Path, Attrs) -> 225 xf_request(XF, ?SSH_FXP_SETSTAT, 226 [?uint32(ReqID), 227 ?string_utf8(Path), 228 encode_ATTR(XF#ssh_xfer.vsn, Attrs)]). 229 230 231%% Modify file attributes 232fsetstat(XF, ReqID, Handle, Attrs) -> 233 xf_request(XF, ?SSH_FXP_FSETSTAT, 234 [?uint32(ReqID), 235 ?binary(Handle), 236 encode_ATTR(XF#ssh_xfer.vsn, Attrs)]). 237 238%% Read a symbolic link 239readlink(XF, ReqID, Path) -> 240 xf_request(XF, ?SSH_FXP_READLINK, 241 [?uint32(ReqID), 242 ?string_utf8(Path)]). 243 244 245%% Create a symbolic link 246symlink(XF, ReqID, LinkPath, TargetPath) -> 247 LinkPath1 = unicode:characters_to_binary(LinkPath), 248 TargetPath1 = unicode:characters_to_binary(TargetPath), 249 xf_request(XF, ?SSH_FXP_SYMLINK, 250 [?uint32(ReqID), 251 ?binary(LinkPath1), 252 ?binary(TargetPath1)]). 253 254%% Convert a path into a 'canonical' form 255realpath(XF, ReqID, Path) -> 256 xf_request(XF, ?SSH_FXP_REALPATH, 257 [?uint32(ReqID), 258 ?string_utf8(Path)]). 259 260extended(XF, ReqID, Request, Data) -> 261 xf_request(XF, ?SSH_FXP_EXTENDED, 262 [?uint32(ReqID), 263 ?string(Request), 264 ?binary(Data)]). 265 266 267%% Send xfer request to connection manager 268xf_request(XF, Op, Arg) -> 269 CM = XF#ssh_xfer.cm, 270 Channel = XF#ssh_xfer.channel, 271 Data = if 272 is_binary(Arg) -> 273 Arg; 274 is_list(Arg) -> 275 list_to_binary(Arg) 276 end, 277 Size = 1+size(Data), 278 ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]). 279 280xf_send_reply(#ssh_xfer{cm = CM, channel = Channel}, Op, Arg) -> 281 Data = if 282 is_binary(Arg) -> 283 Arg; 284 is_list(Arg) -> 285 list_to_binary(Arg) 286 end, 287 Size = 1 + size(Data), 288 ssh_connection:send(CM, Channel, [<<?UINT32(Size), Op, Data/binary>>]). 289 290xf_send_name(XF, ReqId, Name, Attr) -> 291 xf_send_names(XF, ReqId, [{Name, Attr}]). 292 293 294xf_send_handle(#ssh_xfer{cm = CM, channel = Channel}, 295 ReqId, Handle) -> 296 HLen = length(Handle), 297 Size = 1 + 4 + 4+HLen, 298 ToSend = [<<?UINT32(Size), ?SSH_FXP_HANDLE, ?UINT32(ReqId), ?UINT32(HLen)>>, 299 Handle], 300 ssh_connection:send(CM, Channel, ToSend). 301 302xf_send_names(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, 303 ReqId, NamesAndAttrs) -> 304 Count = length(NamesAndAttrs), 305 {Data, Len} = encode_names(Vsn, NamesAndAttrs), 306 Size = 1 + 4 + 4 + Len, 307 ToSend = [<<?UINT32(Size), 308 ?SSH_FXP_NAME, 309 ?UINT32(ReqId), 310 ?UINT32(Count)>>, 311 Data], 312 ssh_connection:send(CM, Channel, ToSend). 313 314xf_send_status(XF, ReqId, ErrorCode) -> 315 xf_send_status(XF, ReqId, ErrorCode, ""). 316 317xf_send_status(XF, ReqId, ErrorCode, ErrorMsg) -> 318 xf_send_status(XF, ReqId, ErrorCode, ErrorMsg, <<>>). 319 320xf_send_status(#ssh_xfer{cm = CM, channel = Channel}, 321 ReqId, ErrorCode, ErrorMsg, Data) -> 322 LangTag = "en", 323 ELen = length(ErrorMsg), 324 TLen = 2, %% length(LangTag), 325 Size = 1 + 4 + 4 + 4+ELen + 4+TLen + size(Data), 326 ToSend = [<<?UINT32(Size), ?SSH_FXP_STATUS, ?UINT32(ReqId), 327 ?UINT32(ErrorCode)>>, 328 <<?UINT32(ELen)>>, ErrorMsg, 329 <<?UINT32(TLen)>>, LangTag, 330 Data], 331 ssh_connection:send(CM, Channel, ToSend). 332 333xf_send_attr(#ssh_xfer{cm = CM, channel = Channel, vsn = Vsn}, ReqId, Attr) -> 334 EncAttr = encode_ATTR(Vsn, Attr), 335 ALen = size(EncAttr), 336 Size = 1 + 4 + ALen, 337 ToSend = [<<?UINT32(Size), ?SSH_FXP_ATTRS, ?UINT32(ReqId)>>, EncAttr], 338 ssh_connection:send(CM, Channel, ToSend). 339 340xf_send_data(#ssh_xfer{cm = CM, channel = Channel}, ReqId, Data) -> 341 DLen = size(Data), 342 Size = 1 + 4 + 4+DLen, 343 ToSend = [<<?UINT32(Size), ?SSH_FXP_DATA, ?UINT32(ReqId), ?UINT32(DLen)>>, 344 Data], 345 ssh_connection:send(CM, Channel, ToSend). 346 347xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status), 348 ?UINT32(ELen), Err:ELen/binary, 349 ?UINT32(LLen), Lang:LLen/binary, 350 Reply/binary >> ) -> 351 Stat = decode_status(Status), 352 {status, ReqID, {Stat,binary_to_list(Err),binary_to_list(Lang), 353 Reply}}; 354xf_reply(_XF, << ?SSH_FXP_STATUS, ?UINT32(ReqID), ?UINT32(Status)>> ) -> 355 Stat = decode_status(Status), 356 {status, ReqID, {Stat,"","",<<>>}}; 357xf_reply(_XF, <<?SSH_FXP_HANDLE, ?UINT32(ReqID), 358 ?UINT32(HLen), Handle:HLen/binary>>) -> 359 {handle, ReqID, Handle}; 360xf_reply(_XF, <<?SSH_FXP_DATA, ?UINT32(ReqID), 361 ?UINT32(DLen), Data:DLen/binary>>) -> 362 {data, ReqID, Data}; 363xf_reply(XF, <<?SSH_FXP_NAME, ?UINT32(ReqID), 364 ?UINT32(Count), AData/binary>>) -> 365 {name, ReqID, decode_names(XF#ssh_xfer.vsn, Count, AData)}; 366xf_reply(XF, <<?SSH_FXP_ATTRS, ?UINT32(ReqID), 367 AData/binary>>) -> 368 {A, _} = decode_ATTR(XF#ssh_xfer.vsn, AData), 369 {attrs, ReqID, A}; 370xf_reply(_XF, <<?SSH_FXP_EXTENDED_REPLY, ?UINT32(ReqID), 371 RData>>) -> 372 {extended_reply, ReqID, RData}. 373 374 375 376decode_status(Status) -> 377 case Status of 378 ?SSH_FX_OK -> ok; 379 ?SSH_FX_EOF -> eof; 380 ?SSH_FX_NO_SUCH_FILE -> no_such_file; 381 ?SSH_FX_PERMISSION_DENIED -> permission_denied; 382 ?SSH_FX_FAILURE -> failure; 383 ?SSH_FX_BAD_MESSAGE -> bad_message; 384 ?SSH_FX_NO_CONNECTION -> no_connection; 385 ?SSH_FX_CONNECTION_LOST -> connection_lost; 386 ?SSH_FX_OP_UNSUPPORTED -> op_unsupported; 387 ?SSH_FX_INVALID_HANDLE -> invalid_handle; 388 ?SSH_FX_NO_SUCH_PATH -> no_such_path; 389 ?SSH_FX_FILE_ALREADY_EXISTS -> file_already_exists; 390 ?SSH_FX_WRITE_PROTECT -> write_protect; 391 ?SSH_FX_NO_MEDIA -> no_media; 392 ?SSH_FX_NO_SPACE_ON_FILESYSTEM -> no_space_on_filesystem; 393 ?SSH_FX_QUOTA_EXCEEDED -> quota_exceeded; 394 ?SSH_FX_UNKNOWN_PRINCIPLE -> unknown_principle; 395 ?SSH_FX_LOCK_CONFlICT -> lock_conflict; 396 ?SSH_FX_NOT_A_DIRECTORY -> not_a_directory; 397 ?SSH_FX_FILE_IS_A_DIRECTORY -> file_is_a_directory; 398 ?SSH_FX_CANNOT_DELETE -> cannot_delete; 399 _ -> {error,Status} 400 end. 401 402encode_erlang_status(Status) -> 403 case Status of 404 ok -> ?SSH_FX_OK; 405 eof -> ?SSH_FX_EOF; 406 enoent -> ?SSH_FX_NO_SUCH_FILE; 407 eacces -> ?SSH_FX_PERMISSION_DENIED; 408 eisdir -> ?SSH_FX_FILE_IS_A_DIRECTORY; 409 eperm -> ?SSH_FX_CANNOT_DELETE; 410 eexist -> ?SSH_FX_FILE_ALREADY_EXISTS; 411 _ -> ?SSH_FX_FAILURE 412 end. 413 414decode_ext(<<?UINT32(NameLen), Name:NameLen/binary, 415 ?UINT32(DataLen), Data:DataLen/binary, 416 Tail/binary>>) -> 417 [{binary_to_list(Name), binary_to_list(Data)} 418 | decode_ext(Tail)]; 419decode_ext(<<>>) -> 420 []. 421 422%% 423%% Encode rename flags 424%% 425encode_rename_flags(Flags) -> 426 encode_bits( 427 fun(overwrite) -> ?SSH_FXP_RENAME_OVERWRITE; 428 (atomic) -> ?SSH_FXP_RENAME_ATOMIC; 429 (native) -> ?SSH_FXP_RENAME_NATIVE 430 end, Flags). 431 432%% decode_rename_flags(F) -> 433%% decode_bits(F, 434%% [{?SSH_FXP_RENAME_OVERWRITE, overwrite}, 435%% {?SSH_FXP_RENAME_ATOMIC, atomic}, 436%% {?SSH_FXP_RENAME_NATIVE, native}]). 437 438 439encode_open_flags(Flags) -> 440 encode_bits( 441 fun (read) -> ?SSH_FXF_READ; 442 (write) -> ?SSH_FXF_WRITE; 443 (append) -> ?SSH_FXF_APPEND; 444 (creat) -> ?SSH_FXF_CREAT; 445 (trunc) -> ?SSH_FXF_TRUNC; 446 (excl) -> ?SSH_FXF_EXCL; 447 (create_new) -> ?SSH_FXF_CREATE_NEW; 448 (create_truncate) -> ?SSH_FXF_CREATE_TRUNCATE; 449 (open_existing) -> ?SSH_FXF_OPEN_EXISTING; 450 (open_or_create) -> ?SSH_FXF_OPEN_OR_CREATE; 451 (truncate_existing) -> ?SSH_FXF_TRUNCATE_EXISTING; 452 (append_data) -> ?SSH_FXF_ACCESS_APPEND_DATA; 453 (append_data_atomic) -> ?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC; 454 (text_mode) -> ?SSH_FXF_ACCESS_TEXT_MODE; 455 (read_lock) -> ?SSH_FXF_ACCESS_READ_LOCK; 456 (write_lock) -> ?SSH_FXF_ACCESS_WRITE_LOCK; 457 (delete_lock) -> ?SSH_FXF_ACCESS_DELETE_LOCK 458 end, Flags). 459 460encode_ace_mask(Access) -> 461 encode_bits( 462 fun(read_data) -> ?ACE4_READ_DATA; 463 (list_directory) -> ?ACE4_LIST_DIRECTORY; 464 (write_data) -> ?ACE4_WRITE_DATA; 465 (add_file) -> ?ACE4_ADD_FILE; 466 (append_data) -> ?ACE4_APPEND_DATA; 467 (add_subdirectory) -> ?ACE4_ADD_SUBDIRECTORY; 468 (read_named_attrs) -> ?ACE4_READ_NAMED_ATTRS; 469 (write_named_attrs) -> ?ACE4_WRITE_NAMED_ATTRS; 470 (execute) -> ?ACE4_EXECUTE; 471 (delete_child) -> ?ACE4_DELETE_CHILD; 472 (read_attributes) -> ?ACE4_READ_ATTRIBUTES; 473 (write_attributes) -> ?ACE4_WRITE_ATTRIBUTES; 474 (delete) -> ?ACE4_DELETE; 475 (read_acl) -> ?ACE4_READ_ACL; 476 (write_acl) -> ?ACE4_WRITE_ACL; 477 (write_owner) -> ?ACE4_WRITE_OWNER; 478 (synchronize) -> ?ACE4_SYNCHRONIZE 479 end, Access). 480 481decode_ace_mask(F) -> 482 decode_bits(F, 483 [ 484 {?ACE4_READ_DATA, read_data}, 485 {?ACE4_LIST_DIRECTORY, list_directory}, 486 {?ACE4_WRITE_DATA, write_data}, 487 {?ACE4_ADD_FILE, add_file}, 488 {?ACE4_APPEND_DATA, append_data}, 489 {?ACE4_ADD_SUBDIRECTORY, add_subdirectory}, 490 {?ACE4_READ_NAMED_ATTRS, read_named_attrs}, 491 {?ACE4_WRITE_NAMED_ATTRS, write_named_attrs}, 492 {?ACE4_EXECUTE, execute}, 493 {?ACE4_DELETE_CHILD, delete_child}, 494 {?ACE4_READ_ATTRIBUTES, read_attributes}, 495 {?ACE4_WRITE_ATTRIBUTES, write_attributes}, 496 {?ACE4_DELETE, delete}, 497 {?ACE4_READ_ACL, read_acl}, 498 {?ACE4_WRITE_ACL, write_acl}, 499 {?ACE4_WRITE_OWNER, write_owner}, 500 {?ACE4_SYNCHRONIZE, synchronize} 501 ]). 502 503decode_open_flags(Vsn, F) when Vsn =< 3 -> 504 decode_bits(F, 505 [ 506 {?SSH_FXF_READ, read}, 507 {?SSH_FXF_WRITE, write}, 508 {?SSH_FXF_APPEND, append}, 509 {?SSH_FXF_CREAT, creat}, 510 {?SSH_FXF_TRUNC, trunc}, 511 {?SSH_FXF_EXCL, excl} 512 ]); 513decode_open_flags(Vsn, F) when Vsn >= 4 -> 514 R = decode_bits(F, 515 [ 516 {?SSH_FXF_ACCESS_APPEND_DATA, append_data}, 517 {?SSH_FXF_ACCESS_APPEND_DATA_ATOMIC, append_data_atomic}, 518 {?SSH_FXF_ACCESS_TEXT_MODE, text_mode}, 519 {?SSH_FXF_ACCESS_READ_LOCK, read_lock}, 520 {?SSH_FXF_ACCESS_WRITE_LOCK, write_lock}, 521 {?SSH_FXF_ACCESS_DELETE_LOCK, delete_lock} 522 ]), 523 AD = case F band ?SSH_FXF_ACCESS_DISPOSITION of 524 ?SSH_FXF_CREATE_NEW -> create_new; 525 ?SSH_FXF_CREATE_TRUNCATE -> create_truncate; 526 ?SSH_FXF_OPEN_EXISTING -> open_existing; 527 ?SSH_FXF_OPEN_OR_CREATE -> open_or_create; 528 ?SSH_FXF_TRUNCATE_EXISTING -> truncate_existing 529 end, 530 [AD | R]. 531 532encode_ace_type(Type) -> 533 case Type of 534 access_allowed -> ?ACE4_ACCESS_ALLOWED_ACE_TYPE; 535 access_denied -> ?ACE4_ACCESS_DENIED_ACE_TYPE; 536 system_audit -> ?ACE4_SYSTEM_AUDIT_ACE_TYPE; 537 system_alarm -> ?ACE4_SYSTEM_ALARM_ACE_TYPE 538 end. 539 540decode_ace_type(F) -> 541 case F of 542 ?ACE4_ACCESS_ALLOWED_ACE_TYPE -> access_allowed; 543 ?ACE4_ACCESS_DENIED_ACE_TYPE -> access_denied; 544 ?ACE4_SYSTEM_AUDIT_ACE_TYPE -> system_audit; 545 ?ACE4_SYSTEM_ALARM_ACE_TYPE -> system_alarm 546 end. 547 548encode_ace_flag(Flag) -> 549 encode_bits( 550 fun(file_inherit) -> ?ACE4_FILE_INHERIT_ACE; 551 (directory_inherit) -> ?ACE4_DIRECTORY_INHERIT_ACE; 552 (no_propagte_inherit) -> ?ACE4_NO_PROPAGATE_INHERIT_ACE; 553 (inherit_only) -> ?ACE4_INHERIT_ONLY_ACE; 554 (successful_access) -> ?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG; 555 (failed_access) -> ?ACE4_FAILED_ACCESS_ACE_FLAG; 556 (identifier_group) -> ?ACE4_IDENTIFIER_GROUP 557 end, Flag). 558 559decode_ace_flag(F) -> 560 decode_bits(F, 561 [ 562 {?ACE4_FILE_INHERIT_ACE, file_inherit}, 563 {?ACE4_DIRECTORY_INHERIT_ACE, directory_inherit}, 564 {?ACE4_NO_PROPAGATE_INHERIT_ACE, no_propagte_inherit}, 565 {?ACE4_INHERIT_ONLY_ACE, inherit_only}, 566 {?ACE4_SUCCESSFUL_ACCESS_ACE_FLAG, successful_access}, 567 {?ACE4_FAILED_ACCESS_ACE_FLAG, failed_access}, 568 {?ACE4_IDENTIFIER_GROUP, identifier_group} 569 ]). 570 571encode_attr_flags(Vsn, all) -> 572 encode_attr_flags(Vsn, 573 [size, uidgid, permissions, 574 acmodtime, accesstime, createtime, 575 modifytime, acl, ownergroup, subsecond_times, 576 bits, extended]); 577encode_attr_flags(Vsn, Flags) -> 578 encode_bits( 579 fun(size) -> ?SSH_FILEXFER_ATTR_SIZE; 580 (uidgid) when Vsn =<3 -> ?SSH_FILEXFER_ATTR_UIDGID; 581 (permissions) -> ?SSH_FILEXFER_ATTR_PERMISSIONS; 582 (acmodtime) when Vsn =< 3 -> ?SSH_FILEXFER_ATTR_ACMODTIME; 583 (accesstime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACCESSTIME; 584 (createtime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_CREATETIME; 585 (modifytime) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_MODIFYTIME; 586 (acl) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_ACL; 587 (ownergroup) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_OWNERGROUP; 588 (subsecond_times) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES; 589 (bits) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_BITS; 590 (extended) when Vsn >= 5 -> ?SSH_FILEXFER_ATTR_EXTENDED; 591 (_) -> 0 592 end, Flags). 593 594encode_file_type(Type) -> 595 case Type of 596 regular -> ?SSH_FILEXFER_TYPE_REGULAR; 597 directory -> ?SSH_FILEXFER_TYPE_DIRECTORY; 598 symlink -> ?SSH_FILEXFER_TYPE_SYMLINK; 599 special -> ?SSH_FILEXFER_TYPE_SPECIAL; 600 unknown -> ?SSH_FILEXFER_TYPE_UNKNOWN; 601 other -> ?SSH_FILEXFER_TYPE_UNKNOWN; 602 socket -> ?SSH_FILEXFER_TYPE_SOCKET; 603 char_device -> ?SSH_FILEXFER_TYPE_CHAR_DEVICE; 604 block_device -> ?SSH_FILEXFER_TYPE_BLOCK_DEVICE; 605 fifo -> ?SSH_FILEXFER_TYPE_FIFO; 606 undefined -> ?SSH_FILEXFER_TYPE_UNKNOWN 607 end. 608 609decode_file_type(Type) -> 610 case Type of 611 ?SSH_FILEXFER_TYPE_REGULAR -> regular; 612 ?SSH_FILEXFER_TYPE_DIRECTORY -> directory; 613 ?SSH_FILEXFER_TYPE_SYMLINK -> symlink; 614 ?SSH_FILEXFER_TYPE_SPECIAL -> special; 615 ?SSH_FILEXFER_TYPE_UNKNOWN -> other; % unknown 616 ?SSH_FILEXFER_TYPE_SOCKET -> socket; 617 ?SSH_FILEXFER_TYPE_CHAR_DEVICE -> char_device; 618 ?SSH_FILEXFER_TYPE_BLOCK_DEVICE -> block_device; 619 ?SSH_FILEXFER_TYPE_FIFO -> fifo 620 end. 621 622encode_attrib_bits(Bits) -> 623 encode_bits( 624 fun(readonly) -> ?SSH_FILEXFER_ATTR_FLAGS_READONLY; 625 (system) -> ?SSH_FILEXFER_ATTR_FLAGS_SYSTEM; 626 (hidden) -> ?SSH_FILEXFER_ATTR_FLAGS_HIDDEN; 627 (case_insensitive) -> ?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE; 628 (arcive) -> ?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE; 629 (encrypted) -> ?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED; 630 (compressed) -> ?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED; 631 (sparse) -> ?SSH_FILEXFER_ATTR_FLAGS_SPARSE; 632 (append_only) -> ?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY; 633 (immutable) -> ?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE; 634 (sync) -> ?SSH_FILEXFER_ATTR_FLAGS_SYNC 635 end, Bits). 636 637decode_attrib_bits(F) -> 638 decode_bits(F, 639 [{?SSH_FILEXFER_ATTR_FLAGS_READONLY, readonly}, 640 {?SSH_FILEXFER_ATTR_FLAGS_SYSTEM, system}, 641 {?SSH_FILEXFER_ATTR_FLAGS_HIDDEN, hidden}, 642 {?SSH_FILEXFER_ATTR_FLAGS_CASE_INSENSITIVE, case_insensitive}, 643 {?SSH_FILEXFER_ATTR_FLAGS_ARCHIVE, arcive}, 644 {?SSH_FILEXFER_ATTR_FLAGS_ENCRYPTED, encrypted}, 645 {?SSH_FILEXFER_ATTR_FLAGS_COMPRESSED, compressed}, 646 {?SSH_FILEXFER_ATTR_FLAGS_SPARSE, sparse}, 647 {?SSH_FILEXFER_ATTR_FLAGS_APPEND_ONLY, append_only}, 648 {?SSH_FILEXFER_ATTR_FLAGS_IMMUTABLE, immutable}, 649 {?SSH_FILEXFER_ATTR_FLAGS_SYNC, sync}]). 650 651 652%% 653%% Encode file attributes 654%% 655encode_ATTR(Vsn, A) -> 656 {Flags,As} = 657 encode_As(Vsn, 658 [{size, A#ssh_xfer_attr.size}, 659 {ownergroup, A#ssh_xfer_attr.owner}, 660 {ownergroup, A#ssh_xfer_attr.group}, 661 {permissions, A#ssh_xfer_attr.permissions}, 662 {acmodtime, A#ssh_xfer_attr.atime}, 663 {acmodtime, A#ssh_xfer_attr.mtime}, 664 {accesstime, A#ssh_xfer_attr.atime}, 665 {subsecond_times, A#ssh_xfer_attr.atime_nseconds}, 666 {createtime, A#ssh_xfer_attr.createtime}, 667 {subsecond_times, A#ssh_xfer_attr.createtime_nseconds}, 668 {modifytime, A#ssh_xfer_attr.mtime}, 669 {subsecond_times, A#ssh_xfer_attr.mtime_nseconds}, 670 {acl, A#ssh_xfer_attr.acl}, 671 {bits, A#ssh_xfer_attr.attrib_bits}, 672 {extended, A#ssh_xfer_attr.extensions}], 673 0, []), 674 Type = encode_file_type(A#ssh_xfer_attr.type), 675 Result = list_to_binary([?uint32(Flags), 676 if Vsn >= 5 -> 677 ?byte(Type); 678 true -> 679 (<<>>) 680 end, As]), 681 Result. 682 683 684encode_As(Vsn, [{_AName, undefined}|As], Flags, Acc) -> 685 encode_As(Vsn, As, Flags, Acc); 686encode_As(Vsn, [{AName, X}|As], Flags, Acc) -> 687 case AName of 688 size -> 689 encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_SIZE, 690 [?uint64(X) | Acc]); 691 ownergroup when Vsn=<4 -> 692 encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_UIDGID, 693 [?uint32(X) | Acc]); 694 ownergroup when Vsn>=5 -> 695 X1 = list_to_binary(integer_to_list(X)), % TODO: check owner and group 696 encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_OWNERGROUP, 697 [?binary(X1) | Acc]); 698 permissions -> 699 encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_PERMISSIONS, 700 [?uint32(X) | Acc]); 701 acmodtime when Vsn=<3 -> 702 encode_As(Vsn, As,Flags bor ?SSH_FILEXFER_ATTR_ACMODTIME, 703 [?uint32(X) | Acc]); 704 accesstime when Vsn>=5 -> 705 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACCESSTIME, 706 [?uint64(X) | Acc]); 707 createtime when Vsn>=5-> 708 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_CREATETIME, 709 [?uint64(X) | Acc]); 710 modifytime when Vsn>=5 -> 711 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_MODIFYTIME, 712 [?uint64(X) | Acc]); 713 subsecond_times when Vsn>=5 -> 714 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_SUBSECOND_TIMES, 715 [?uint64(X) | Acc]); 716 acl when Vsn >=5 -> 717 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_ACL, 718 [encode_acl(X) | Acc]); 719 bits when Vsn>=5 -> 720 F = encode_attrib_bits(X), 721 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_BITS, 722 [?uint32(F) | Acc]); 723 extended -> 724 encode_As(Vsn, As, Flags bor ?SSH_FILEXFER_ATTR_EXTENDED, 725 [encode_extensions(X) | Acc]); 726 _ -> 727 encode_As(Vsn, As, Flags, Acc) 728 end; 729encode_As(_Vsn, [], Flags, Acc) -> 730 {Flags, reverse(Acc)}. 731 732 733decode_ATTR(Vsn, <<?UINT32(Flags), Tail/binary>>) -> 734 {Type,Tail2} = 735 if Vsn =< 3 -> 736 {?SSH_FILEXFER_TYPE_UNKNOWN, Tail}; 737 true -> 738 <<?BYTE(T), TL/binary>> = Tail, 739 {T, TL} 740 end, 741 decode_As(Vsn, 742 [{size, #ssh_xfer_attr.size}, 743 {ownergroup, #ssh_xfer_attr.owner}, 744 {ownergroup, #ssh_xfer_attr.group}, 745 {permissions, #ssh_xfer_attr.permissions}, 746 {acmodtime, #ssh_xfer_attr.atime}, 747 {acmodtime, #ssh_xfer_attr.mtime}, 748 {accesstime, #ssh_xfer_attr.atime}, 749 {subsecond_times, #ssh_xfer_attr.atime_nseconds}, 750 {createtime, #ssh_xfer_attr.createtime}, 751 {subsecond_times, #ssh_xfer_attr.createtime_nseconds}, 752 {modifytime, #ssh_xfer_attr.mtime}, 753 {subsecond_times, #ssh_xfer_attr.mtime_nseconds}, 754 {acl, #ssh_xfer_attr.acl}, 755 {bits, #ssh_xfer_attr.attrib_bits}, 756 {extended, #ssh_xfer_attr.extensions}], 757 #ssh_xfer_attr { type = decode_file_type(Type) }, 758 Flags, 759 Tail2). 760 761decode_As(Vsn, [{AName, AField}|As], R, Flags, Tail) -> 762 case AName of 763 size when ?is_set(?SSH_FILEXFER_ATTR_SIZE, Flags) -> 764 <<?UINT64(X), Tail2/binary>> = Tail, 765 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 766 ownergroup when ?is_set(?SSH_FILEXFER_ATTR_UIDGID, Flags),Vsn=<3 -> 767 <<?UINT32(X), Tail2/binary>> = Tail, 768 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 769 ownergroup when ?is_set(?SSH_FILEXFER_ATTR_OWNERGROUP, Flags),Vsn>=5 -> 770 <<?UINT32(Len), Bin:Len/binary, Tail2/binary>> = Tail, 771 X = binary_to_list(Bin), 772 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 773 774 permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn>=5-> 775 <<?UINT32(X), Tail2/binary>> = Tail, 776 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 777 778 permissions when ?is_set(?SSH_FILEXFER_ATTR_PERMISSIONS,Flags),Vsn=<3-> 779 <<?UINT32(X), Tail2/binary>> = Tail, 780 R1 = setelement(AField, R, X), 781 Type = case X band ?S_IFMT of 782 ?S_IFDIR -> directory; 783 ?S_IFCHR -> char_device; 784 ?S_IFBLK -> block_device; 785 ?S_IFIFO -> fifi; 786 ?S_IFREG -> regular; 787 ?S_IFSOCK -> socket; 788 ?S_IFLNK -> symlink; 789 _ -> unknown 790 end, 791 decode_As(Vsn, As, R1#ssh_xfer_attr { type=Type}, Flags, Tail2); 792 793 acmodtime when ?is_set(?SSH_FILEXFER_ATTR_ACMODTIME,Flags),Vsn=<3 -> 794 <<?UINT32(X), Tail2/binary>> = Tail, 795 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 796 accesstime when ?is_set(?SSH_FILEXFER_ATTR_ACCESSTIME,Flags),Vsn>=5 -> 797 <<?UINT64(X), Tail2/binary>> = Tail, 798 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 799 modifytime when ?is_set(?SSH_FILEXFER_ATTR_MODIFYTIME,Flags),Vsn>=5 -> 800 <<?UINT64(X), Tail2/binary>> = Tail, 801 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 802 createtime when ?is_set(?SSH_FILEXFER_ATTR_CREATETIME,Flags),Vsn>=5 -> 803 <<?UINT64(X), Tail2/binary>> = Tail, 804 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 805 subsecond_times when ?is_set(?SSH_FILEXFER_ATTR_SUBSECOND_TIMES,Flags),Vsn>=5 -> 806 <<?UINT32(X), Tail2/binary>> = Tail, 807 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 808 acl when ?is_set(?SSH_FILEXFER_ATTR_ACL, Flags), Vsn>=5 -> 809 {X,Tail2} = decode_acl(Tail), 810 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 811 bits when ?is_set(?SSH_FILEXFER_ATTR_BITS, Flags), Vsn >=5 -> 812 <<?UINT32(Y), Tail2/binary>> = Tail, 813 X = decode_attrib_bits(Y), 814 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 815 extended when ?is_set(?SSH_FILEXFER_ATTR_EXTENDED, Flags) -> 816 {X,Tail2} = decode_extended(Tail), 817 decode_As(Vsn, As, setelement(AField, R, X), Flags, Tail2); 818 _ -> 819 decode_As(Vsn, As, R, Flags, Tail) 820 end; 821decode_As(_Vsn, [], R, _, Tail) -> 822 {R, Tail}. 823 824 825 826 827decode_names(_Vsn, 0, _Data) -> 828 []; 829decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary, 830 ?UINT32(LLen), _LongName:LLen/binary, 831 Tail/binary>>) when Vsn =< 3 -> 832 Name = unicode:characters_to_list(FileName), 833 {A, Tail2} = decode_ATTR(Vsn, Tail), 834 [{Name, A} | decode_names(Vsn, I-1, Tail2)]; 835decode_names(Vsn, I, <<?UINT32(Len), FileName:Len/binary, 836 Tail/binary>>) when Vsn >= 4 -> 837 Name = unicode:characters_to_list(FileName), 838 {A, Tail2} = decode_ATTR(Vsn, Tail), 839 [{Name, A} | decode_names(Vsn, I-1, Tail2)]. 840 841encode_names(Vsn, NamesAndAttrs) -> 842 lists:mapfoldl(fun(N, L) -> encode_name(Vsn, N, L) end, 0, NamesAndAttrs). 843 844encode_name(Vsn, {NameUC,Attr}, Len) when Vsn =< 3 -> 845 Name = binary_to_list(unicode:characters_to_binary(NameUC)), 846 NLen = length(Name), 847 EncAttr = encode_ATTR(Vsn, Attr), 848 ALen = size(EncAttr), 849 NewLen = Len + NLen*2 + 4 + 4 + ALen, 850 {[<<?UINT32(NLen)>>, Name, <<?UINT32(NLen)>>, Name, EncAttr], NewLen}; 851encode_name(Vsn, {NameUC,Attr}, Len) when Vsn >= 4 -> 852 Name = binary_to_list(unicode:characters_to_binary(NameUC)), 853 NLen = length(Name), 854 EncAttr = encode_ATTR(Vsn, Attr), 855 ALen = size(EncAttr), 856 {[<<?UINT32(NLen)>>, Name, EncAttr], 857 Len + 4 + NLen + ALen}. 858 859encode_acl(ACLList) -> 860 Count = length(ACLList), 861 [?uint32(Count) | encode_acl_items(ACLList)]. 862 863encode_acl_items([ACE|As]) -> 864 Type = encode_ace_type(ACE#ssh_xfer_ace.type), 865 Flag = encode_ace_flag(ACE#ssh_xfer_ace.flag), 866 Mask = encode_ace_mask(ACE#ssh_xfer_ace.mask), 867 Who = ACE#ssh_xfer_ace.who, 868 [?uint32(Type), ?uint32(Flag), ?uint32(Mask), 869 ?string_utf8(Who) | encode_acl_items(As)]; 870encode_acl_items([]) -> 871 []. 872 873 874decode_acl(<<?UINT32(Count), Tail/binary>>) -> 875 decode_acl_items(Count, Tail, []). 876 877decode_acl_items(0, Tail, Acc) -> 878 {reverse(Acc), Tail}; 879decode_acl_items(I, <<?UINT32(Type), 880 ?UINT32(Flag), 881 ?UINT32(Mask), 882 ?UINT32(WLen), BWho:WLen/binary, 883 Tail/binary>>, Acc) -> 884 decode_acl_items(I-1, Tail, 885 [#ssh_xfer_ace { type = decode_ace_type(Type), 886 flag = decode_ace_flag(Flag), 887 mask = decode_ace_mask(Mask), 888 who = unicode:characters_to_list(BWho)} | Acc]). 889 890encode_extensions(Exts) -> 891 Count = length(Exts), 892 [?uint32(Count) | encode_ext(Exts)]. 893 894encode_ext([{Type, Data} | Exts]) -> 895 [?string(Type), ?string(Data) | encode_ext(Exts)]; 896encode_ext([]) -> 897 []. 898 899 900decode_extended(<<?UINT32(Count), Tail/binary>>) -> 901 decode_ext(Count, Tail, []). 902 903decode_ext(0, Tail, Acc) -> 904 {reverse(Acc), Tail}; 905decode_ext(I, <<?UINT32(TLen), Type:TLen/binary, 906 ?UINT32(DLen), Data:DLen/binary, 907 Tail/binary>>, Acc) -> 908 decode_ext(I-1, Tail, [{binary_to_list(Type), Data}|Acc]). 909 910 911 912%% Encode bit encoded flags 913encode_bits(Fun, BitNames) -> 914 encode_bits(Fun, 0, BitNames). 915 916encode_bits(Fun, F, [Bit|BitNames]) -> 917 encode_bits(Fun, Fun(Bit) bor F, BitNames); 918encode_bits(_Fun, F, []) -> 919 F. 920 921%% Decode bit encoded flags 922decode_bits(F, [{Bit,BitName}|Bits]) -> 923 if F band Bit == Bit -> 924 [BitName | decode_bits(F, Bits)]; 925 true -> 926 decode_bits(F, Bits) 927 end; 928decode_bits(_F, []) -> 929 []. 930