~ubuntu-branches/debian/squeeze/erlang/squeeze

« back to all changes in this revision

Viewing changes to lib/ssh/src/ssh_sftp.erl

  • Committer: Bazaar Package Importer
  • Author(s): Erlang Packagers, Sergei Golovan
  • Date: 2006-12-03 17:07:44 UTC
  • mfrom: (2.1.11 feisty)
  • Revision ID: james.westby@ubuntu.com-20061203170744-rghjwupacqlzs6kv
Tags: 1:11.b.2-4
[ Sergei Golovan ]
Fixed erlang-base and erlang-base-hipe prerm scripts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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/.
 
6
%% 
 
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
 
10
%% under the License.
 
11
%% 
 
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.''
 
15
%% 
 
16
%%     $Id$
 
17
%%
 
18
 
 
19
%%% Description: SFTP protocol front-end
 
20
 
 
21
-module(ssh_sftp).
 
22
 
 
23
-behaviour(gen_server).
 
24
%%--------------------------------------------------------------------
 
25
%% Include files
 
26
%%--------------------------------------------------------------------
 
27
-include_lib("kernel/include/file.hrl").
 
28
 
 
29
-include("ssh.hrl").
 
30
-include("ssh_xfer.hrl").
 
31
 
 
32
-import(lists, [member/2, map/2, foldl/3, reverse/1, foreach/2]).
 
33
%%--------------------------------------------------------------------
 
34
%% External exports
 
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]).
 
39
 
 
40
-export([open_mode/2]).
 
41
 
 
42
%% API
 
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]).
 
49
 
 
50
%% gen_server callbacks
 
51
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
 
52
         code_change/3]).
 
53
 
 
54
%% Other exports
 
55
-export([info_to_attr/1, attr_to_info/1]).
 
56
 
 
57
-record(state, 
 
58
        {
 
59
          xf,
 
60
          rep_buf = <<>>,
 
61
          req_id,
 
62
          req_list = []  %% {ReqId, Fun}
 
63
         }).
 
64
 
 
65
-define(FILEOP_TIMEOUT, 60000).
 
66
 
 
67
-define(NEXT_REQID(S),
 
68
        S#state { req_id = (S#state.req_id + 1) band 16#ffffffff}).
 
69
 
 
70
-define(XF(S), S#state.xf).
 
71
-define(REQID(S), S#state.req_id).
 
72
 
 
73
%%====================================================================
 
74
%% External functions
 
75
%%====================================================================
 
76
open(Pid, File, Mode) ->
 
77
    gen_server:call(Pid, {open, false, File, Mode}, ?FILEOP_TIMEOUT).
 
78
 
 
79
opendir(Pid, Path) ->
 
80
    gen_server:call(Pid, {opendir, false, Path}, ?FILEOP_TIMEOUT).
 
81
 
 
82
close(Pid, Handle) ->
 
83
    gen_server:call(Pid, {close,false,Handle}, ?FILEOP_TIMEOUT).
 
84
 
 
85
readdir(Pid,Handle) ->
 
86
    gen_server:call(Pid, {readdir,false,Handle}, ?FILEOP_TIMEOUT).
 
87
 
 
88
pread(Pid, Handle, Offset, Len) ->
 
89
    gen_server:call(Pid, {pread,false,Handle, Offset, Len}, ?FILEOP_TIMEOUT).
 
90
 
 
91
read(Pid, Handle, Len) ->
 
92
    gen_server:call(Pid, {read,false,Handle, Len}, ?FILEOP_TIMEOUT).    
 
93
 
 
94
apread(Pid, Handle, Offset, Len) ->
 
95
    gen_server:call(Pid, {pread,true,Handle, Offset, Len}, ?FILEOP_TIMEOUT).
 
96
 
 
97
aread(Pid, Handle, Len) ->
 
98
    gen_server:call(Pid, {read,true,Handle, Len}, ?FILEOP_TIMEOUT).    
 
99
 
 
100
pwrite(Pid, Handle, Offset, Data) ->
 
101
    gen_server:call(Pid, {pwrite,false,Handle,Offset,Data}, ?FILEOP_TIMEOUT).
 
102
 
 
103
write(Pid, Handle, Data) ->
 
104
    gen_server:call(Pid, {write,false,Handle,Data}, ?FILEOP_TIMEOUT).
 
105
 
 
106
apwrite(Pid, Handle, Offset, Data) ->
 
107
    gen_server:call(Pid, {pwrite,true,Handle,Offset,Data}, ?FILEOP_TIMEOUT).
 
108
 
 
109
awrite(Pid, Handle, Data) ->
 
110
    gen_server:call(Pid, {write,true,Handle,Data}, ?FILEOP_TIMEOUT).
 
111
 
 
112
position(Pid, Handle, Pos) ->
 
113
    gen_server:call(Pid, {position, Handle, Pos}, ?FILEOP_TIMEOUT).
 
114
 
 
115
real_path(Pid, Path) ->
 
116
    gen_server:call(Pid, {real_path, false, Path}, ?FILEOP_TIMEOUT).
 
117
 
 
118
read_file_info(Pid, Name) ->
 
119
    gen_server:call(Pid, {read_file_info,false,Name}, ?FILEOP_TIMEOUT).
 
120
 
 
121
get_file_info(Pid, Handle) ->
 
122
    gen_server:call(Pid, {get_file_info,false,Handle}, ?FILEOP_TIMEOUT).
 
123
 
 
124
write_file_info(Pid, Name, Info) ->
 
125
    gen_server:call(Pid, {write_file_info,false,Name, Info}, ?FILEOP_TIMEOUT).
 
126
 
 
127
read_link_info(Pid, Name) ->
 
128
    gen_server:call(Pid, {read_link_info,false,Name}, ?FILEOP_TIMEOUT).
 
129
 
 
130
read_link(Pid, Name) ->
 
131
    gen_server:call(Pid, {read_link,false,Name}, ?FILEOP_TIMEOUT).
 
132
 
 
133
make_symlink(Pid, Old, New) ->
 
134
    gen_server:call(Pid, {make_symlink,false,Old,New}, ?FILEOP_TIMEOUT).    
 
135
 
 
136
rename(Pid, FromFile, ToFile) ->
 
137
    gen_server:call(Pid, {rename,false,FromFile, ToFile}, ?FILEOP_TIMEOUT).
 
138
 
 
139
delete(Pid, Name) ->
 
140
    gen_server:call(Pid, {delete,false,Name}, ?FILEOP_TIMEOUT).
 
141
 
 
142
make_dir(Pid, Name) ->
 
143
    gen_server:call(Pid, {make_dir,false,Name}, ?FILEOP_TIMEOUT).
 
144
 
 
145
del_dir(Pid, Name) ->
 
146
    gen_server:call(Pid, {del_dir,false,Name}, ?FILEOP_TIMEOUT).
 
147
 
 
148
 
 
149
stop(Pid) ->
 
150
    gen_server:call(Pid, stop).
 
151
 
 
152
send_window(Pid) ->
 
153
    gen_server:call(Pid, send_window, ?FILEOP_TIMEOUT).
 
154
 
 
155
recv_window(Pid) ->
 
156
    gen_server:call(Pid, recv_window, ?FILEOP_TIMEOUT).
 
157
 
 
158
 
 
159
list_dir(Pid, Name) ->
 
160
    case opendir(Pid, Name) of
 
161
        {ok,Handle} ->
 
162
            Res = do_list_dir(Pid, Handle, []),
 
163
            close(Pid, Handle),
 
164
            case Res of
 
165
                {ok, List} ->
 
166
                    NList = foldl(fun({Nm, _Info},Acc) -> 
 
167
                                          [Nm|Acc] end, 
 
168
                                  [], List),
 
169
                    {ok,NList};
 
170
                Error -> Error
 
171
            end;
 
172
        Error ->
 
173
            Error
 
174
    end.
 
175
 
 
176
do_list_dir(Pid, Handle, Acc) ->
 
177
    case readdir(Pid, Handle) of
 
178
        {name, Names} ->
 
179
            do_list_dir(Pid, Handle, Acc ++ Names);
 
180
        eof ->
 
181
            {ok, Acc};
 
182
        Error ->
 
183
            Error
 
184
    end.
 
185
 
 
186
 
 
187
read_file(Pid, Name) ->
 
188
    case open(Pid, Name, [read, binary]) of
 
189
        {ok, Handle} ->
 
190
            {ok,{_WindowSz,PacketSz}} = recv_window(Pid),
 
191
            Res = read_file_loop(Pid, Handle, PacketSz, []),
 
192
            close(Pid, Handle),
 
193
            Res;
 
194
        Error ->
 
195
            Error
 
196
    end.
 
197
 
 
198
read_file_loop(Pid, Handle, PacketSz, Acc) ->
 
199
    case read(Pid, Handle, PacketSz) of
 
200
        {ok, Data}  ->
 
201
            read_file_loop(Pid, Handle, PacketSz, [Data|Acc]);
 
202
        eof ->
 
203
            {ok, list_to_binary(reverse(Acc))};
 
204
        Error ->
 
205
            Error
 
206
    end.
 
207
 
 
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
 
212
        {ok, Handle} ->
 
213
            {ok,{_Window,Packet}} = send_window(Pid),
 
214
            Res = write_file_loop(Pid, Handle, 0, Bin, size(Bin), Packet),
 
215
            close(Pid, Handle),
 
216
            Res;
 
217
        Error ->
 
218
            Error
 
219
    end.
 
220
 
 
221
write_file_loop(_Pid, _Handle, _Pos, _Bin, 0, _PacketSz) ->
 
222
    ok;
 
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
 
227
                ok ->
 
228
                    write_file_loop(Pid, Handle, 
 
229
                                    Pos+PacketSz, Bin, Remain-PacketSz,
 
230
                                    PacketSz);
 
231
                Error ->
 
232
                    Error
 
233
            end;
 
234
       true ->
 
235
            <<_:Pos/binary, Data/binary>> = Bin,
 
236
            write(Pid, Handle, Data)
 
237
    end.
 
238
 
 
239
 
 
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, []], []).
 
248
 
 
249
%% start_link(Host, Opts) ->
 
250
%%     gen_server:start_link(?MODULE, [Host, 22, Opts], []).
 
251
    
 
252
%% start_link(Host, Port, Opts) ->
 
253
%%     gen_server:start_link(?MODULE, [Host, Port, Opts], []).
 
254
 
 
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, []], []).
 
259
 
 
260
connect(Host, Opts) ->
 
261
    gen_server:start(?MODULE, [Host, 22, Opts], []).
 
262
    
 
263
connect(Host, Port, Opts) ->
 
264
    gen_server:start(?MODULE, [Host, Port, Opts], []).
 
265
 
 
266
%%====================================================================
 
267
%% Server functions
 
268
%%====================================================================
 
269
 
 
270
%%--------------------------------------------------------------------
 
271
%% Function: init/1
 
272
%% Description: Initiates the server
 
273
%% Returns: {ok, State}          |
 
274
%%          {ok, State, Timeout} |
 
275
%%          ignore               |
 
276
%%          {stop, Reason}
 
277
%%--------------------------------------------------------------------
 
278
init([CM]) ->
 
279
    case ssh_xfer:attach(CM, ?FILEOP_TIMEOUT) of
 
280
        {ok,Xf,RBuf} ->
 
281
            {ok, #state { req_id = 0, xf = Xf, rep_buf=RBuf }};
 
282
        Error ->
 
283
            {stop, Error }
 
284
    end;
 
285
init([Host,Port,Opts]) ->
 
286
    SaveFlag = process_flag(trap_exit, true),
 
287
    case ssh_xfer:connect(Host, Port, Opts) of
 
288
        {ok, Xf, RBuf} ->
 
289
            process_flag(trap_exit, SaveFlag),
 
290
            {ok, #state { req_id = 0, xf = Xf, rep_buf=RBuf }};
 
291
        Error ->
 
292
            {stop, Error}
 
293
    end.
 
294
 
 
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) ->
 
306
    XF = St#state.xf,
 
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),
 
310
    case Async of
 
311
        true ->
 
312
            {reply, {async,ReqID},
 
313
             wait_req(ReqID, St,
 
314
                      fun({ok,Handle},St1) ->
 
315
                              open2(ReqID,FileName,Handle,Mode,Async,From,St1);
 
316
                         (Rep,St1) ->
 
317
                              async_reply(ReqID, Rep, From, St1)
 
318
                      end)};
 
319
        false ->
 
320
            {noreply,
 
321
             wait_req(ReqID, St,
 
322
                      fun({ok,Handle},St1) ->
 
323
                              open2(ReqID,FileName,Handle,Mode,Async,From,St1);
 
324
                         (Rep,St1) ->
 
325
                              sync_reply(Rep, From, St1)
 
326
                      end)}
 
327
    end;
 
328
 
 
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);
 
333
 
 
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);
 
338
 
 
339
handle_call({close,_Async,Handle}, From, St) ->
 
340
    %% wait until all operations on handle are done
 
341
    case get({size, Handle}) of
 
342
        undefined ->
 
343
            ReqID = St#state.req_id,
 
344
            ssh_xfer:close(?XF(St), ReqID, Handle),
 
345
            make_reply_post(ReqID, false, From, St,
 
346
                            fun(Rep) ->
 
347
                                    erase({offset,Handle}),
 
348
                                    erase({size,Handle}),
 
349
                                    erase({mode,Handle}),
 
350
                                    Rep
 
351
                            end);
 
352
        _ ->
 
353
            case lseek_position(Handle, cur) of
 
354
                {ok,_} ->
 
355
                    ReqID = St#state.req_id,
 
356
                    ssh_xfer:close(?XF(St), ReqID, Handle),
 
357
                    make_reply_post(ReqID, false, From, St,
 
358
                                    fun(Rep) ->
 
359
                                            erase({offset,Handle}),
 
360
                                            erase({size,Handle}),
 
361
                                            erase({mode,Handle}),
 
362
                                    Rep
 
363
                                    end);
 
364
                Error ->
 
365
                    {reply,Error, St}
 
366
            end
 
367
    end;
 
368
 
 
369
handle_call({pread,Async,Handle,At,Length}, From, St) ->
 
370
    case lseek_position(Handle, At) of
 
371
        {ok,Offset} ->
 
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,
 
378
                            fun({ok,Data}) ->
 
379
                                    case get({mode,Handle}) of
 
380
                                        binary -> {ok,Data};
 
381
                                        text -> {ok,binary_to_list(Data)}
 
382
                                    end;
 
383
                               (Rep) -> 
 
384
                                    Rep
 
385
                            end);
 
386
        Error ->
 
387
            {reply, Error, St}
 
388
    end;
 
389
 
 
390
handle_call({read,Async,Handle,Length}, From, St) ->
 
391
    case lseek_position(Handle,cur) of
 
392
        {ok,Offset} ->
 
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,
 
399
                            fun({ok,Data}) ->
 
400
                                    case get({mode,Handle}) of
 
401
                                        binary -> {ok,Data};
 
402
                                        text -> {ok,binary_to_list(Data)}
 
403
                                    end;
 
404
                               (Rep) -> Rep
 
405
                            end);
 
406
        Error ->
 
407
            {reply, Error, St}
 
408
    end;
 
409
 
 
410
handle_call({pwrite,Async,Handle,At,Data0}, From, St) ->
 
411
    case lseek_position(Handle, At) of
 
412
        {ok,Offset} ->
 
413
            Data = if binary(Data0) -> Data0;
 
414
                      list(Data0) -> list_to_binary(Data0)
 
415
                   end,
 
416
            ReqID = St#state.req_id,
 
417
            Size = size(Data),
 
418
            ssh_xfer:write(?XF(St),ReqID,Handle,Offset,Data),
 
419
            update_size(Handle, Offset+Size),
 
420
            make_reply(ReqID, Async, From, St);
 
421
        Error ->
 
422
            {reply, Error, St}
 
423
    end;
 
424
 
 
425
handle_call({write,Async,Handle,Data0}, From, St) ->
 
426
    case lseek_position(Handle, cur) of
 
427
        {ok,Offset} ->
 
428
            Data = if binary(Data0) -> Data0;
 
429
                      list(Data0) -> list_to_binary(Data0)
 
430
                   end,
 
431
            ReqID = St#state.req_id,
 
432
            Size = size(Data),
 
433
            ssh_xfer:write(?XF(St),ReqID,Handle,Offset,Data),
 
434
            update_offset(Handle, Offset+Size),
 
435
            make_reply(ReqID, Async, From, St);
 
436
        Error ->
 
437
            {reply, Error, St}
 
438
    end;
 
439
 
 
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
 
443
        {ok,Offset} ->
 
444
            update_offset(Handle, Offset),
 
445
            {reply, {ok, Offset}, St};
 
446
        Error ->
 
447
            {reply, Error, St}
 
448
    end;
 
449
 
 
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);
 
454
 
 
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);
 
459
 
 
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);
 
465
 
 
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);
 
470
 
 
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);
 
475
 
 
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);
 
480
 
 
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);
 
485
 
 
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);
 
490
 
 
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);
 
495
 
 
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);
 
501
 
 
502
handle_call(send_window, _From, St) ->
 
503
    XF = St#state.xf,
 
504
    {reply, ssh_cm:send_window(XF#ssh_xfer.cm, XF#ssh_xfer.channel,
 
505
                              ?FILEOP_TIMEOUT), St};
 
506
 
 
507
handle_call(recv_window, _From, St) ->
 
508
    XF = St#state.xf,
 
509
    {reply, ssh_cm:recv_window(XF#ssh_xfer.cm, XF#ssh_xfer.channel,
 
510
                               ?FILEOP_TIMEOUT), St};
 
511
 
 
512
handle_call(stop, _From, St) ->
 
513
    XF = St#state.xf,
 
514
    #ssh_xfer{cm = CM, channel = Channel} = XF,
 
515
    ssh_cm:close(CM, Channel),
 
516
    ssh_cm:stop(CM),
 
517
    {stop, normal, ok, St};
 
518
 
 
519
handle_call(Call, _From, St) ->    
 
520
    {reply, {error, bad_call, Call}, St}.
 
521
 
 
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) ->
 
530
    {noreply, St}.
 
531
 
 
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)),
 
541
    if Type == 0 ->
 
542
            Data0 = St#state.rep_buf,
 
543
            St1 = handle_reply(St,CM,Channel,<<Data0/binary,Data/binary>>),
 
544
            {noreply, St1};
 
545
       true ->
 
546
            error_logger:format("ssh: STDERR: ~s\n", [binary_to_list(Data)]),
 
547
            {noreply, St}
 
548
    end;
 
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]),
 
553
    {stop, normal, St1};
 
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]),
 
558
    {stop, normal, St1};
 
559
handle_info({ssh_cm, CM, {eof, Channel}},St) ->
 
560
    St1 = reply_all(St, CM, Channel, eof),
 
561
    ?dbg(true, "handle_info: eof \n", []),
 
562
    {stop, normal, St1};
 
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", []),
 
566
    {stop, normal, St1};
 
567
handle_info(_Info, State) ->
 
568
    ?dbg(true, "sftp: got info ~p\n", [_Info]),
 
569
    {noreply, State}.
 
570
 
 
571
%%--------------------------------------------------------------------
 
572
%% Function: terminate/2
 
573
%% Description: Shutdown the server
 
574
%% Returns: any (ignored by gen_server)
 
575
%%--------------------------------------------------------------------
 
576
terminate(_Reason, _State) ->
 
577
    ok.
 
578
 
 
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) ->
 
585
    {ok, State}.
 
586
 
 
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)
 
596
    end,
 
597
    ReqID = St#state.req_id,
 
598
    ssh_xfer:stat(St#state.xf, ReqID, FileName, [size]),
 
599
    case Async of
 
600
        true ->
 
601
            wait_req(ReqID, St,
 
602
                     fun({ok,FI},St1) ->
 
603
                             if integer(FI#file_info.size) ->
 
604
                                     put({size,Handle}, FI#file_info.size);
 
605
                                true ->
 
606
                                     ok
 
607
                             end,
 
608
                             async_reply(OrigReqID, {ok,Handle}, From, St1);
 
609
                        (_, St1) ->
 
610
                             async_reply(OrigReqID, {ok,Handle}, From, St1)
 
611
                     end);
 
612
        false ->
 
613
            wait_req(ReqID, St,
 
614
                     fun({ok,FI},St1) ->
 
615
                             if is_integer(FI#file_info.size) ->
 
616
                                     put({size,Handle}, FI#file_info.size);
 
617
                                true ->
 
618
                                     ok
 
619
                             end,
 
620
                             sync_reply({ok,Handle}, From,St1);
 
621
                         (_, St1) ->
 
622
                             sync_reply({ok,Handle}, From,St1)
 
623
                     end)
 
624
    end.
 
625
 
 
626
 
 
627
 
 
628
async_reply(ReqID, Reply, _From={To,_}, St) ->
 
629
    To ! {async_reply, ReqID, Reply},
 
630
    St.
 
631
 
 
632
 
 
633
sync_reply(Reply, From, St) ->
 
634
    gen:reply(From, Reply),
 
635
    St.
 
636
 
 
637
 
 
638
reply_all(St, _Cm, _Channel, Reply) ->
 
639
    List = St#state.req_list,
 
640
    foreach(fun({_ReqID,Fun}) ->
 
641
                    catch Fun(Reply,St)
 
642
            end, List),
 
643
    St#state {�req_list = []}.
 
644
 
 
645
 
 
646
make_reply(ReqID, true, From, St) ->
 
647
    {reply, {async, ReqID},
 
648
     wait_req(ReqID, St,
 
649
              fun(Reply,St1) -> 
 
650
                      async_reply(ReqID,Reply,From,St1)
 
651
              end)};
 
652
make_reply(ReqID, false, From, St) ->
 
653
    {noreply, 
 
654
     wait_req(ReqID, St,
 
655
              fun(Reply,St1) -> 
 
656
                      sync_reply(Reply, From, St1) 
 
657
              end)}.
 
658
 
 
659
make_reply_post(ReqID, true, From, St, PostFun) ->
 
660
    {reply, {async, ReqID},
 
661
     wait_req(ReqID, St,
 
662
              fun(Reply,St1) ->
 
663
                      case catch PostFun(Reply) of
 
664
                          {'EXIT',_} ->
 
665
                              async_reply(ReqID,Reply, From, St1);
 
666
                          Reply1 ->
 
667
                              async_reply(ReqID,Reply1, From, St1)
 
668
                      end
 
669
              end)};
 
670
make_reply_post(ReqID, false, From, St, PostFun) ->
 
671
    {noreply,
 
672
     wait_req(ReqID, St,
 
673
              fun(Reply,St1) ->
 
674
                      case catch PostFun(Reply) of
 
675
                          {'EXIT',_} ->
 
676
                              sync_reply(Reply, From, St1);
 
677
                          Reply1 ->
 
678
                              sync_reply(Reply1, From, St1)
 
679
                      end
 
680
              end)}.
 
681
 
 
682
 
 
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 }.
 
687
 
 
688
handle_reply(St, Cm, Channel, Data) ->
 
689
    case Data of
 
690
        <<?UINT32(Len), RData:Len/binary, RBuf/binary>> ->
 
691
            case catch ssh_xfer:xf_reply(?XF(St), RData) of
 
692
                {'EXIT', _Reason} ->
 
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)
 
698
            end;
 
699
        RBuf ->
 
700
            St#state { rep_buf = RBuf }
 
701
    end.
 
702
 
 
703
handle_req_reply(St, ReqID, XfReply) ->
 
704
    case lists:keysearch(ReqID, 1, St#state.req_list) of
 
705
        false ->
 
706
            error_logger:format("handle_req_reply: req_id=~p not found\n",
 
707
                                [ReqID]),
 
708
            St;
 
709
        {value,{_,Fun}} ->
 
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
 
713
                {'EXIT', _} ->  St1;
 
714
                St2 -> St2
 
715
            end
 
716
    end.
 
717
 
 
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}.
 
727
 
 
728
 
 
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)}.
 
739
 
 
740
%% convert: ssh_xfer_attr -> file_info
 
741
attr_to_info(A) when is_record(A, ssh_xfer_attr) ->
 
742
    #file_info{
 
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,
 
750
      links  = 1,
 
751
      major_device = 0,
 
752
      minor_device = 0,
 
753
      inode  = 0,
 
754
      uid    = A#ssh_xfer_attr.owner,
 
755
      gid    = A#ssh_xfer_attr.group}.
 
756
 
 
757
unix_to_datetime(undefined) ->
 
758
    undefined;
 
759
unix_to_datetime(Sec) ->
 
760
    calendar:gregorian_seconds_to_datetime(Sec + 62167219200).
 
761
 
 
762
datetime_to_unix(undefined) ->
 
763
    undefined;
 
764
datetime_to_unix(DateTime) ->
 
765
    calendar:datetime_to_gregorian_seconds(DateTime) - 62167219200.
 
766
 
 
767
 
 
768
open_mode(Vsn,Modes) when Vsn >= 5 ->
 
769
    open_mode5(Modes);
 
770
open_mode(_Vsn, Modes) ->
 
771
    open_mode3(Modes).
 
772
 
 
773
open_mode5(Modes) ->
 
774
    A = #ssh_xfer_attr{type = regular},
 
775
    {Fl, Ac} = case {member(write, Modes),
 
776
                     member(read, Modes),
 
777
                     member(append, Modes)} of
 
778
                   {_, _, true} ->
 
779
                       {[append_data],
 
780
                        [read_attributes,
 
781
                         append_data, write_attributes]};
 
782
                   {true, false, false} ->
 
783
                       {[create_truncate],
 
784
                        [write_data, write_attributes]};
 
785
                   {true, true, _} ->
 
786
                       {[open_or_create],
 
787
                        [read_data, read_attributes,
 
788
                         write_data, write_attributes]};
 
789
                   {false, true, _} ->
 
790
                       {[open_existing],
 
791
                        [read_data, read_attributes]}
 
792
               end,
 
793
    {Ac, Fl, A}.
 
794
 
 
795
open_mode3(Modes) ->
 
796
    A = #ssh_xfer_attr{type = regular},
 
797
    Fl = case {member(write, Modes),
 
798
               member(read, Modes),
 
799
               member(append, Modes)} of
 
800
             {_, _, true} ->
 
801
                 [append];
 
802
             {true, false, false} ->
 
803
                 [write, creat, trunc];
 
804
             {true, true, _} ->
 
805
                 [read, write];
 
806
             {false, true, _} ->
 
807
                 [read]
 
808
         end,
 
809
    {[], Fl, A}.
 
810
 
 
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) ->
 
814
%%     Flags1 =
 
815
%%      case member(write, Mode) orelse member(truncate_existing, Flags) of
 
816
%%          false -> [open_existing | Flags];
 
817
%%          true  -> Flags
 
818
%%      end,
 
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) ->
 
824
%%     Flags1 =
 
825
%%      case member(read, Mode) orelse member(existing, Flags) of
 
826
%%          true -> Flags;
 
827
%%          false -> [create_truncate|Flags]
 
828
%%      end,
 
829
%%     open_mode(5, Mode, [write_data,write_attributes|Access], 
 
830
%%            Flags1, Attrs);
 
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],
 
837
%%            Attrs);
 
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}.
 
844
 
 
845
 
 
846
update_size(Handle, NewSize) ->
 
847
    OldSize = get({size,Handle}),
 
848
    if NewSize > OldSize ->
 
849
            put({size,Handle}, NewSize);
 
850
       true ->
 
851
            ok
 
852
    end.
 
853
 
 
854
%% set_offset(Handle, NewOffset) ->
 
855
%%     put({offset,Handle}, NewOffset).
 
856
 
 
857
update_offset(Handle, NewOffset) ->
 
858
    put({offset,Handle}, NewOffset),
 
859
    update_size(Handle, NewOffset).
 
860
 
 
861
%%
 
862
%% Caluclate a integer offset
 
863
%%
 
864
lseek_position(Handle, Pos) ->
 
865
    lseek_pos(Pos, get({offset,Handle}), get({size,Handle})).
 
866
 
 
867
lseek_pos(_Pos, undefined, _) ->
 
868
    {error, einval};
 
869
lseek_pos(Pos, _CurOffset, _CurSize)
 
870
  when integer(Pos), 0 =< Pos, Pos < ?SSH_FILEXFER_LARGEFILESIZE ->
 
871
    {ok,Pos};
 
872
lseek_pos(bof, _CurOffset, _CurSize) ->
 
873
    {ok,0};
 
874
lseek_pos(cur, CurOffset, _CurSize) ->
 
875
    {ok,CurOffset};
 
876
lseek_pos(eof, _CurOffset, CurSize) ->
 
877
    {ok,CurSize};
 
878
lseek_pos({bof, Offset}, _CurOffset, _CurSize)
 
879
  when integer(Offset), 0 =< Offset, Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
 
880
    {ok, Offset};
 
881
lseek_pos({cur, Offset}, CurOffset, _CurSize)
 
882
  when integer(Offset), -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset, 
 
883
       Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
 
884
    NewOffset = CurOffset + Offset,
 
885
    if NewOffset < 0 ->
 
886
            {ok, 0};
 
887
       true ->
 
888
            {ok, NewOffset}
 
889
    end;
 
890
lseek_pos({eof, Offset}, _CurOffset, CurSize) 
 
891
  when integer(Offset), -(?SSH_FILEXFER_LARGEFILESIZE) =< Offset, 
 
892
       Offset < ?SSH_FILEXFER_LARGEFILESIZE ->
 
893
    NewOffset = CurSize + Offset,
 
894
    if NewOffset < 0 ->
 
895
            {ok, 0};
 
896
       true ->
 
897
            {ok, NewOffset}
 
898
    end;
 
899
lseek_pos(_, _, _) ->
 
900
    {error, einval}.
 
901