1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
19
%%% Description: SFTP protocol front-end
23
-behaviour(gen_server).
24
%%--------------------------------------------------------------------
26
%%--------------------------------------------------------------------
27
-include_lib("kernel/include/file.hrl").
30
-include("ssh_xfer.hrl").
32
-import(lists, [member/2, map/2, foldl/3, reverse/1, foreach/2]).
33
%%--------------------------------------------------------------------
35
%% -export([start/3, start_link/3]).
36
%% -export([start/2, start_link/2]).
37
%% -export([start/1, start_link/1]).
38
-export([connect/1, connect/2, connect/3]).
40
-export([open_mode/2]).
43
-export([open/3, opendir/2, close/2, readdir/2, pread/4, read/3,
44
apread/4, aread/3, pwrite/4, write/3, apwrite/4, awrite/3,
45
position/3, real_path/2, read_file_info/2, get_file_info/2,
46
write_file_info/3, read_link_info/2, read_link/2, make_symlink/3,
47
rename/3, delete/2, make_dir/2, del_dir/2, stop/1, send_window/1,
48
recv_window/1, list_dir/2, read_file/2, write_file/3]).
50
%% gen_server callbacks
51
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
55
-export([info_to_attr/1, attr_to_info/1]).
62
req_list = [] %% {ReqId, Fun}
65
-define(FILEOP_TIMEOUT, 60000).
67
-define(NEXT_REQID(S),
68
S#state { req_id = (S#state.req_id + 1) band 16#ffffffff}).
70
-define(XF(S), S#state.xf).
71
-define(REQID(S), S#state.req_id).
73
%%====================================================================
75
%%====================================================================
76
open(Pid, File, Mode) ->
77
gen_server:call(Pid, {open, false, File, Mode}, ?FILEOP_TIMEOUT).
80
gen_server:call(Pid, {opendir, false, Path}, ?FILEOP_TIMEOUT).
83
gen_server:call(Pid, {close,false,Handle}, ?FILEOP_TIMEOUT).
85
readdir(Pid,Handle) ->
86
gen_server:call(Pid, {readdir,false,Handle}, ?FILEOP_TIMEOUT).
88
pread(Pid, Handle, Offset, Len) ->
89
gen_server:call(Pid, {pread,false,Handle, Offset, Len}, ?FILEOP_TIMEOUT).
91
read(Pid, Handle, Len) ->
92
gen_server:call(Pid, {read,false,Handle, Len}, ?FILEOP_TIMEOUT).
94
apread(Pid, Handle, Offset, Len) ->
95
gen_server:call(Pid, {pread,true,Handle, Offset, Len}, ?FILEOP_TIMEOUT).
97
aread(Pid, Handle, Len) ->
98
gen_server:call(Pid, {read,true,Handle, Len}, ?FILEOP_TIMEOUT).
100
pwrite(Pid, Handle, Offset, Data) ->
101
gen_server:call(Pid, {pwrite,false,Handle,Offset,Data}, ?FILEOP_TIMEOUT).
103
write(Pid, Handle, Data) ->
104
gen_server:call(Pid, {write,false,Handle,Data}, ?FILEOP_TIMEOUT).
106
apwrite(Pid, Handle, Offset, Data) ->
107
gen_server:call(Pid, {pwrite,true,Handle,Offset,Data}, ?FILEOP_TIMEOUT).
109
awrite(Pid, Handle, Data) ->
110
gen_server:call(Pid, {write,true,Handle,Data}, ?FILEOP_TIMEOUT).
112
position(Pid, Handle, Pos) ->
113
gen_server:call(Pid, {position, Handle, Pos}, ?FILEOP_TIMEOUT).
115
real_path(Pid, Path) ->
116
gen_server:call(Pid, {real_path, false, Path}, ?FILEOP_TIMEOUT).
118
read_file_info(Pid, Name) ->
119
gen_server:call(Pid, {read_file_info,false,Name}, ?FILEOP_TIMEOUT).
121
get_file_info(Pid, Handle) ->
122
gen_server:call(Pid, {get_file_info,false,Handle}, ?FILEOP_TIMEOUT).
124
write_file_info(Pid, Name, Info) ->
125
gen_server:call(Pid, {write_file_info,false,Name, Info}, ?FILEOP_TIMEOUT).
127
read_link_info(Pid, Name) ->
128
gen_server:call(Pid, {read_link_info,false,Name}, ?FILEOP_TIMEOUT).
130
read_link(Pid, Name) ->
131
gen_server:call(Pid, {read_link,false,Name}, ?FILEOP_TIMEOUT).
133
make_symlink(Pid, Old, New) ->
134
gen_server:call(Pid, {make_symlink,false,Old,New}, ?FILEOP_TIMEOUT).
136
rename(Pid, FromFile, ToFile) ->
137
gen_server:call(Pid, {rename,false,FromFile, ToFile}, ?FILEOP_TIMEOUT).
140
gen_server:call(Pid, {delete,false,Name}, ?FILEOP_TIMEOUT).
142
make_dir(Pid, Name) ->
143
gen_server:call(Pid, {make_dir,false,Name}, ?FILEOP_TIMEOUT).
145
del_dir(Pid, Name) ->
146
gen_server:call(Pid, {del_dir,false,Name}, ?FILEOP_TIMEOUT).
150
gen_server:call(Pid, stop).
153
gen_server:call(Pid, send_window, ?FILEOP_TIMEOUT).
156
gen_server:call(Pid, recv_window, ?FILEOP_TIMEOUT).
159
list_dir(Pid, Name) ->
160
case opendir(Pid, Name) of
162
Res = do_list_dir(Pid, Handle, []),
166
NList = foldl(fun({Nm, _Info},Acc) ->
176
do_list_dir(Pid, Handle, Acc) ->
177
case readdir(Pid, Handle) of
179
do_list_dir(Pid, Handle, Acc ++ Names);
187
read_file(Pid, Name) ->
188
case open(Pid, Name, [read, binary]) of
190
{ok,{_WindowSz,PacketSz}} = recv_window(Pid),
191
Res = read_file_loop(Pid, Handle, PacketSz, []),
198
read_file_loop(Pid, Handle, PacketSz, Acc) ->
199
case read(Pid, Handle, PacketSz) of
201
read_file_loop(Pid, Handle, PacketSz, [Data|Acc]);
203
{ok, list_to_binary(reverse(Acc))};
208
write_file(Pid, Name, List) when list(List) ->
209
write_file(Pid, Name, list_to_binary(List));
210
write_file(Pid, Name, Bin) ->
211
case open(Pid, Name, [write, binary]) of
213
{ok,{_Window,Packet}} = send_window(Pid),
214
Res = write_file_loop(Pid, Handle, 0, Bin, size(Bin), Packet),
221
write_file_loop(_Pid, _Handle, _Pos, _Bin, 0, _PacketSz) ->
223
write_file_loop(Pid, Handle, Pos, Bin, Remain, PacketSz) ->
224
if Remain >= PacketSz ->
225
<<_:Pos/binary, Data:PacketSz/binary, _/binary>> = Bin,
226
case write(Pid, Handle, Data) of
228
write_file_loop(Pid, Handle,
229
Pos+PacketSz, Bin, Remain-PacketSz,
235
<<_:Pos/binary, Data/binary>> = Bin,
236
write(Pid, Handle, Data)
240
%%--------------------------------------------------------------------
241
%% Function: start_link/0
242
%% Description: Starts the server
243
%%--------------------------------------------------------------------
244
%% start_link(CM) when is_pid(CM) ->
245
%% gen_server:start_link(?MODULE, [CM], []);
246
%% start_link(Host) when is_list(Host) ->
247
%% gen_server:start_link(?MODULE, [Host, 22, []], []).
249
%% start_link(Host, Opts) ->
250
%% gen_server:start_link(?MODULE, [Host, 22, Opts], []).
252
%% start_link(Host, Port, Opts) ->
253
%% gen_server:start_link(?MODULE, [Host, Port, Opts], []).
255
connect(CM) when is_pid(CM) ->
256
gen_server:start(?MODULE, [CM], []);
257
connect(Host) when is_list(Host) ->
258
gen_server:start(?MODULE, [Host, 22, []], []).
260
connect(Host, Opts) ->
261
gen_server:start(?MODULE, [Host, 22, Opts], []).
263
connect(Host, Port, Opts) ->
264
gen_server:start(?MODULE, [Host, Port, Opts], []).
266
%%====================================================================
268
%%====================================================================
270
%%--------------------------------------------------------------------
272
%% Description: Initiates the server
273
%% Returns: {ok, State} |
274
%% {ok, State, Timeout} |
277
%%--------------------------------------------------------------------
279
case ssh_xfer:attach(CM, ?FILEOP_TIMEOUT) of
281
{ok, #state { req_id = 0, xf = Xf, rep_buf=RBuf }};
285
init([Host,Port,Opts]) ->
286
SaveFlag = process_flag(trap_exit, true),
287
case ssh_xfer:connect(Host, Port, Opts) of
289
process_flag(trap_exit, SaveFlag),
290
{ok, #state { req_id = 0, xf = Xf, rep_buf=RBuf }};
295
%%--------------------------------------------------------------------
296
%% Function: handle_call/3
297
%% Description: Handling call messages
298
%% Returns: {reply, Reply, State} |
299
%% {reply, Reply, State, Timeout} |
300
%% {noreply, State} |
301
%% {noreply, State, Timeout} |
302
%% {stop, Reason, Reply, State} | (terminate/2 is called)
303
%% {stop, Reason, State} (terminate/2 is called)
304
%%--------------------------------------------------------------------
305
handle_call({open,Async,FileName,Mode}, From, St) ->
307
{Access,Flags,Attrs} = open_mode(XF#ssh_xfer.vsn, Mode),
308
ReqID = St#state.req_id,
309
ssh_xfer:open(XF, ReqID, FileName, Access, Flags, Attrs),
312
{reply, {async,ReqID},
314
fun({ok,Handle},St1) ->
315
open2(ReqID,FileName,Handle,Mode,Async,From,St1);
317
async_reply(ReqID, Rep, From, St1)
322
fun({ok,Handle},St1) ->
323
open2(ReqID,FileName,Handle,Mode,Async,From,St1);
325
sync_reply(Rep, From, St1)
329
handle_call({opendir,Async,Path}, From, St) ->
330
ReqID = St#state.req_id,
331
ssh_xfer:opendir(?XF(St), ReqID, Path),
332
make_reply(ReqID, Async, From, St);
334
handle_call({readdir,Async,Handle}, From, St) ->
335
ReqID = St#state.req_id,
336
ssh_xfer:readdir(?XF(St), ReqID, Handle),
337
make_reply(ReqID, Async, From, St);
339
handle_call({close,_Async,Handle}, From, St) ->
340
%% wait until all operations on handle are done
341
case get({size, Handle}) of
343
ReqID = St#state.req_id,
344
ssh_xfer:close(?XF(St), ReqID, Handle),
345
make_reply_post(ReqID, false, From, St,
347
erase({offset,Handle}),
348
erase({size,Handle}),
349
erase({mode,Handle}),
353
case lseek_position(Handle, cur) of
355
ReqID = St#state.req_id,
356
ssh_xfer:close(?XF(St), ReqID, Handle),
357
make_reply_post(ReqID, false, From, St,
359
erase({offset,Handle}),
360
erase({size,Handle}),
361
erase({mode,Handle}),
369
handle_call({pread,Async,Handle,At,Length}, From, St) ->
370
case lseek_position(Handle, At) of
372
ReqID = St#state.req_id,
373
ssh_xfer:read(?XF(St),ReqID,Handle,Offset,Length),
374
%% To get multiple async read to work we must update the offset
375
%% before the operation begins
376
update_offset(Handle, Offset+Length),
377
make_reply_post(ReqID,Async,From,St,
379
case get({mode,Handle}) of
381
text -> {ok,binary_to_list(Data)}
390
handle_call({read,Async,Handle,Length}, From, St) ->
391
case lseek_position(Handle,cur) of
393
ReqID = St#state.req_id,
394
ssh_xfer:read(?XF(St),ReqID,Handle,Offset,Length),
395
%% To get multiple async read to work we must update the offset
396
%% before the operation begins
397
update_offset(Handle, Offset+Length),
398
make_reply_post(ReqID,Async,From,St,
400
case get({mode,Handle}) of
402
text -> {ok,binary_to_list(Data)}
410
handle_call({pwrite,Async,Handle,At,Data0}, From, St) ->
411
case lseek_position(Handle, At) of
413
Data = if binary(Data0) -> Data0;
414
list(Data0) -> list_to_binary(Data0)
416
ReqID = St#state.req_id,
418
ssh_xfer:write(?XF(St),ReqID,Handle,Offset,Data),
419
update_size(Handle, Offset+Size),
420
make_reply(ReqID, Async, From, St);
425
handle_call({write,Async,Handle,Data0}, From, St) ->
426
case lseek_position(Handle, cur) of
428
Data = if binary(Data0) -> Data0;
429
list(Data0) -> list_to_binary(Data0)
431
ReqID = St#state.req_id,
433
ssh_xfer:write(?XF(St),ReqID,Handle,Offset,Data),
434
update_offset(Handle, Offset+Size),
435
make_reply(ReqID, Async, From, St);
440
handle_call({position,Handle,At}, _From, St) ->
441
%% We could make this auto sync when all request to Handle is done?
442
case lseek_position(Handle, At) of
444
update_offset(Handle, Offset),
445
{reply, {ok, Offset}, St};
450
handle_call({rename,Async,FromFile,ToFile}, From, St) ->
451
ReqID = St#state.req_id,
452
ssh_xfer:rename(?XF(St),ReqID,FromFile,ToFile,[overwrite]),
453
make_reply(ReqID, Async, From, St);
455
handle_call({delete,Async,Name}, From, St) ->
456
ReqID = St#state.req_id,
457
ssh_xfer:remove(?XF(St), ReqID, Name),
458
make_reply(ReqID, Async, From, St);
460
handle_call({make_dir,Async,Name}, From, St) ->
461
ReqID = St#state.req_id,
462
ssh_xfer:mkdir(?XF(St), ReqID, Name,
463
#ssh_xfer_attr{ type = directory }),
464
make_reply(ReqID, Async, From, St);
466
handle_call({del_dir,Async,Name}, From, St) ->
467
ReqID = St#state.req_id,
468
ssh_xfer:rmdir(?XF(St), ReqID, Name),
469
make_reply(ReqID, Async, From, St);
471
handle_call({real_path,Async,Name}, From, St) ->
472
ReqID = St#state.req_id,
473
ssh_xfer:realpath(?XF(St), ReqID, Name),
474
make_reply(ReqID, Async, From, St);
476
handle_call({read_file_info,Async,Name}, From, St) ->
477
ReqID = St#state.req_id,
478
ssh_xfer:stat(?XF(St), ReqID, Name, all),
479
make_reply(ReqID, Async, From, St);
481
handle_call({get_file_info,Async,Name}, From, St) ->
482
ReqID = St#state.req_id,
483
ssh_xfer:fstat(?XF(St), ReqID, Name, all),
484
make_reply(ReqID, Async, From, St);
486
handle_call({read_link_info,Async,Name}, From, St) ->
487
ReqID = St#state.req_id,
488
ssh_xfer:lstat(?XF(St), ReqID, Name, all),
489
make_reply(ReqID, Async, From, St);
491
handle_call({read_link,Async,Name}, From, St) ->
492
ReqID = St#state.req_id,
493
ssh_xfer:readlink(?XF(St), ReqID, Name),
494
make_reply(ReqID, Async, From, St);
496
handle_call({write_file_info,Async,Name,Info}, From, St) ->
497
ReqID = St#state.req_id,
498
A = info_to_attr(Info),
499
ssh_xfer:setstat(?XF(St), ReqID, Name, A),
500
make_reply(ReqID, Async, From, St);
502
handle_call(send_window, _From, St) ->
504
{reply, ssh_cm:send_window(XF#ssh_xfer.cm, XF#ssh_xfer.channel,
505
?FILEOP_TIMEOUT), St};
507
handle_call(recv_window, _From, St) ->
509
{reply, ssh_cm:recv_window(XF#ssh_xfer.cm, XF#ssh_xfer.channel,
510
?FILEOP_TIMEOUT), St};
512
handle_call(stop, _From, St) ->
514
#ssh_xfer{cm = CM, channel = Channel} = XF,
515
ssh_cm:close(CM, Channel),
517
{stop, normal, ok, St};
519
handle_call(Call, _From, St) ->
520
{reply, {error, bad_call, Call}, St}.
522
%%--------------------------------------------------------------------
523
%% Function: handle_cast/2
524
%% Description: Handling cast messages
525
%% Returns: {noreply, State} |
526
%% {noreply, State, Timeout} |
527
%% {stop, Reason, State} (terminate/2 is called)
528
%%--------------------------------------------------------------------
529
handle_cast(_Msg, St) ->
532
%%--------------------------------------------------------------------
533
%% Function: handle_info/2
534
%% Description: Handling all non call/cast messages
535
%% Returns: {noreply, State} |
536
%% {noreply, State, Timeout} |
537
%% {stop, Reason, State} (terminate/2 is called)
538
%%--------------------------------------------------------------------
539
handle_info({ssh_cm, CM, {data,Channel,Type,Data}}, St) ->
540
ssh_cm:adjust_window(CM, Channel, size(Data)),
542
Data0 = St#state.rep_buf,
543
St1 = handle_reply(St,CM,Channel,<<Data0/binary,Data/binary>>),
546
error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]),
549
handle_info({ssh_cm, CM, {exit_signal,Channel,_SIG,Err,_Lang}},St) ->
550
ssh_cm:close(CM, Channel),
551
St1 = reply_all(St, CM, Channel, {error, Err}),
552
?dbg(true, "handle_info: exit_signal ~p ~p ~p\n", [_SIG, Err, _Lang]),
554
handle_info({ssh_cm, CM, {exit_status,Channel,_Status}},St) ->
555
ssh_cm:close(CM, Channel),
556
St1 = reply_all(St, CM, Channel, eof),
557
?dbg(true, "handle_info: exit_status ~p\n", [_Status]),
559
handle_info({ssh_cm, CM, {eof, Channel}},St) ->
560
St1 = reply_all(St, CM, Channel, eof),
561
?dbg(true, "handle_info: eof \n", []),
563
handle_info({ssh_cm, CM, {closed, Channel}},St) ->
564
St1 = reply_all(St, CM, Channel, {error, closed}),
565
?dbg(true, "handle_info: closed\n", []),
567
handle_info(_Info, State) ->
568
?dbg(true, "sftp: got info ~p\n", [_Info]),
571
%%--------------------------------------------------------------------
572
%% Function: terminate/2
573
%% Description: Shutdown the server
574
%% Returns: any (ignored by gen_server)
575
%%--------------------------------------------------------------------
576
terminate(_Reason, _State) ->
579
%%--------------------------------------------------------------------
580
%% Func: code_change/3
581
%% Purpose: Convert process state when code is changed
582
%% Returns: {ok, NewState}
583
%%--------------------------------------------------------------------
584
code_change(_OldVsn, State, _Extra) ->
587
%%--------------------------------------------------------------------
588
%%% Internal functions
589
%%--------------------------------------------------------------------
590
open2(OrigReqID,FileName,Handle,Mode,Async,From,St) ->
591
put({offset,Handle}, 0),
592
put({size,Handle}, 0),
593
case member(binary, Mode) orelse member(raw, Mode) of
594
true -> put({mode,Handle}, binary);
595
false -> put({mode,Handle}, text)
597
ReqID = St#state.req_id,
598
ssh_xfer:stat(St#state.xf, ReqID, FileName, [size]),
603
if integer(FI#file_info.size) ->
604
put({size,Handle}, FI#file_info.size);
608
async_reply(OrigReqID, {ok,Handle}, From, St1);
610
async_reply(OrigReqID, {ok,Handle}, From, St1)
615
if is_integer(FI#file_info.size) ->
616
put({size,Handle}, FI#file_info.size);
620
sync_reply({ok,Handle}, From,St1);
622
sync_reply({ok,Handle}, From,St1)
628
async_reply(ReqID, Reply, _From={To,_}, St) ->
629
To ! {async_reply, ReqID, Reply},
633
sync_reply(Reply, From, St) ->
634
gen:reply(From, Reply),
638
reply_all(St, _Cm, _Channel, Reply) ->
639
List = St#state.req_list,
640
foreach(fun({_ReqID,Fun}) ->
643
St#state {�req_list = []}.
646
make_reply(ReqID, true, From, St) ->
647
{reply, {async, ReqID},
650
async_reply(ReqID,Reply,From,St1)
652
make_reply(ReqID, false, From, St) ->
656
sync_reply(Reply, From, St1)
659
make_reply_post(ReqID, true, From, St, PostFun) ->
660
{reply, {async, ReqID},
663
case catch PostFun(Reply) of
665
async_reply(ReqID,Reply, From, St1);
667
async_reply(ReqID,Reply1, From, St1)
670
make_reply_post(ReqID, false, From, St, PostFun) ->
674
case catch PostFun(Reply) of
676
sync_reply(Reply, From, St1);
678
sync_reply(Reply1, From, St1)
683
wait_req(ReqID, St, Fun) ->
684
List = [{ReqID,Fun} | St#state.req_list],
685
ID = (St#state.req_id + 1) band 16#ffffffff,
686
St#state { req_list = List, req_id = ID }.
688
handle_reply(St, Cm, Channel, Data) ->
690
<<?UINT32(Len), RData:Len/binary, RBuf/binary>> ->
691
case catch ssh_xfer:xf_reply(?XF(St), RData) of
693
?dbg(true, "handle_reply: error ~p\n", [_Reason]),
694
handle_reply(St, Cm, Channel, RBuf);
695
XfReply={_, ReqID, _} ->
696
St1 = handle_req_reply(St, ReqID, XfReply),
697
handle_reply(St1, Cm, Channel, RBuf)
700
St#state { rep_buf = RBuf }
703
handle_req_reply(St, ReqID, XfReply) ->
704
case lists:keysearch(ReqID, 1, St#state.req_list) of
706
error_logger:format("handle_req_reply: req_id=~p not found\n",
710
List = lists:keydelete(ReqID, 1, St#state.req_list),
711
St1 = St#state { req_list = List },
712
case catch Fun(xreply(XfReply),St1) of
718
xreply({handle,_,H}) -> {ok, H};
719
xreply({data,_,Data}) -> {ok, Data};
720
xreply({name,_,Names}) -> {ok, Names};
721
xreply({attrs, _, A}) -> {ok, attr_to_info(A)};
722
xreply({extended_reply,_,X}) -> {ok, X};
723
xreply({status,_,{ok, _Err, _Lang, _Rep}}) -> ok;
724
xreply({status,_,{eof, _Err, _Lang, _Rep}}) -> eof;
725
xreply({status,_,{Stat, _Err, _Lang, _Rep}}) -> {error, Stat};
726
xreply({Code, _, Reply}) -> {Code, Reply}.
729
%% convert: file_info -> ssh_xfer_attr
730
info_to_attr(I) when is_record(I, file_info) ->
731
#ssh_xfer_attr { permissions = I#file_info.mode,
732
size = I#file_info.size,
733
type = I#file_info.type,
734
owner = I#file_info.uid,
735
group = I#file_info.gid,
736
atime = datetime_to_unix(I#file_info.atime),
737
mtime = datetime_to_unix(I#file_info.mtime),
738
createtime = datetime_to_unix(I#file_info.ctime)}.
740
%% convert: ssh_xfer_attr -> file_info
741
attr_to_info(A) when is_record(A, ssh_xfer_attr) ->
743
size = A#ssh_xfer_attr.size,
744
type = A#ssh_xfer_attr.type,
745
access = read_write, %% FIXME: read/write/read_write/none
746
atime = unix_to_datetime(A#ssh_xfer_attr.atime),
747
mtime = unix_to_datetime(A#ssh_xfer_attr.mtime),
748
ctime = unix_to_datetime(A#ssh_xfer_attr.createtime),
749
mode = A#ssh_xfer_attr.permissions,
754
uid = A#ssh_xfer_attr.owner,
755
gid = A#ssh_xfer_attr.group}.
757
unix_to_datetime(undefined) ->
759
unix_to_datetime(Sec) ->
760
calendar:gregorian_seconds_to_datetime(Sec + 62167219200).
762
datetime_to_unix(undefined) ->
764
datetime_to_unix(DateTime) ->
765
calendar:datetime_to_gregorian_seconds(DateTime) - 62167219200.
768
open_mode(Vsn,Modes) when Vsn >= 5 ->
770
open_mode(_Vsn, Modes) ->
774
A = #ssh_xfer_attr{type = regular},
775
{Fl, Ac} = case {member(write, Modes),
777
member(append, Modes)} of
781
append_data, write_attributes]};
782
{true, false, false} ->
784
[write_data, write_attributes]};
787
[read_data, read_attributes,
788
write_data, write_attributes]};
791
[read_data, read_attributes]}
796
A = #ssh_xfer_attr{type = regular},
797
Fl = case {member(write, Modes),
799
member(append, Modes)} of
802
{true, false, false} ->
803
[write, creat, trunc];
811
%% open_mode(3, Mode, Access, Flags, Attrs) ->
812
%% open_mode(3,Mode,[read_data,read_attributes|Access], [read|Flags], Attrs);
813
%% open_mode(5, [read|Mode], Access, Flags, Attrs) ->
815
%% case member(write, Mode) orelse member(truncate_existing, Flags) of
816
%% false -> [open_existing | Flags];
819
%% open_mode(5, Mode, [read_data,read_attributes|Access], Flags1, Attrs);
820
%% open_mode(3, [write|Mode], Access, Flags, Attrs) ->
821
%% open_mode(3, Mode, [write_data,write_attributes|Access],
822
%% [write,creat,trunc|Flags], Attrs);
823
%% open_mode(5, [write|Mode], Access, Flags, Attrs) ->
825
%% case member(read, Mode) orelse member(existing, Flags) of
827
%% false -> [create_truncate|Flags]
829
%% open_mode(5, Mode, [write_data,write_attributes|Access],
831
%% open_mode(3, [append|Mode],Access, Flags, Attrs) ->
832
%% open_mode(3, Mode, [write_data,write_attributes|Access],
833
%% [write,creat,trunc,append|Flags], Attrs);
834
%% open_mode(5, [append|Mode],Access, Flags, Attrs) ->
835
%% open_mode(5, Mode, [write_data,write_attributes,append_data|Access],
836
%% [open_or_create,write_data,write_attributes,append_data|Flags],
838
%% open_mode(Vsn, [raw|Mode],Access, Flags, Attrs) ->
839
%% open_mode(Vsn, Mode, Access, Flags, Attrs);
840
%% open_mode(Vsn, [binary|Mode],Access, Flags, Attrs) ->
841
%% open_mode(Vsn, Mode, Access, Flags, Attrs);
842
%% open_mode(_, [], Access, Flags, Attrs) ->
843
%% {Access, Flags, Attrs}.
846
update_size(Handle, NewSize) ->
847
OldSize = get({size,Handle}),
848
if NewSize > OldSize ->
849
put({size,Handle}, NewSize);
854
%% set_offset(Handle, NewOffset) ->
855
%% put({offset,Handle}, NewOffset).
857
update_offset(Handle, NewOffset) ->
858
put({offset,Handle}, NewOffset),
859
update_size(Handle, NewOffset).
862
%% Caluclate a integer offset
864
lseek_position(Handle, Pos) ->
865
lseek_pos(Pos, get({offset,Handle}), get({size,Handle})).
867
lseek_pos(_Pos, undefined, _) ->
869
lseek_pos(Pos, _CurOffset, _CurSize)
870
when integer(Pos), 0 =< Pos, Pos < ?SSH_FILEXFER_LARGEFILESIZE ->
872
lseek_pos(bof, _CurOffset, _CurSize) ->
874
lseek_pos(cur, CurOffset, _CurSize) ->
876
lseek_pos(eof, _CurOffset, CurSize) ->
878
lseek_pos({bof, Offset}, _CurOffset, _CurSize)
879
when integer(Offset), 0 =< Offset, Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
881
lseek_pos({cur, Offset}, CurOffset, _CurSize)
882
when integer(Offset), -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset,
883
Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
884
NewOffset = CurOffset + Offset,
890
lseek_pos({eof, Offset}, _CurOffset, CurSize)
891
when integer(Offset), -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset,
892
Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
893
NewOffset = CurSize + Offset,
899
lseek_pos(_, _, _) ->