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