network-infra-utility 0.2.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +21 -0
- data/CHANGELOG.md +31 -4
- data/Gemfile +2 -0
- data/Gemfile.lock +70 -0
- data/Rakefile +1 -1
- data/bin/dns-query +834 -0
- data/bin/geo-doc +135 -0
- data/bin/geo-get +1 -1
- data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
- data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
- data/document/Geo/345/221/275/344/273/244/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +441 -0
- data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
- data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
- data/document/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/344/275/277/347/224/250/346/226/271/346/263/225.md +764 -0
- data/network-infra-utility.gemspec +4 -2
- data/network.rb +3 -1
- data/service/geodb/GeoAPI.md +1 -0
- data/service/geodb/geodb.rb +278 -1
- data/service/ssh/README.md +942 -0
- data/service/ssh/bin/ssh-client +198 -0
- data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/345/212/237/350/203/275/351/234/200/346/261/202/346/226/207/346/241/243.md +292 -0
- data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/257/246/347/273/206/350/256/276/350/256/241/346/226/207/346/241/243.md +1521 -0
- data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/275/257/344/273/266/350/256/276/350/256/241/346/226/207/346/241/243.md +2493 -0
- data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
- data/service/ssh/ext/ssh_core/config/sys.config +0 -0
- data/service/ssh/ext/ssh_core/config/vm.args +0 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
- data/service/ssh/ext/ssh_core/rebar.config +24 -0
- data/service/ssh/ext/ssh_core/rebar.lock +1 -0
- data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
- data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
- data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
- data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
- data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
- data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
- data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
- data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
- data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +104 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
- data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
- data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
- data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
- data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
- data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
- data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
- data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
- data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
- data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
- data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
- data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
- data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
- data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
- data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
- data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
- data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
- data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
- data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
- data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
- data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
- data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
- data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
- data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
- data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
- data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
- data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
- data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
- data/support/basic/as_num.rb +221 -0
- data/support/basic/mac_address.rb +281 -0
- data/version.rb +1 -1
- metadata +138 -1
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
-module(ssh_sftp_session).
|
|
2
|
+
-behaviour(gen_server).
|
|
3
|
+
|
|
4
|
+
%% SFTP session — manages a single SFTP channel on an SSH connection.
|
|
5
|
+
%%
|
|
6
|
+
%% Key design (LLD §6.6):
|
|
7
|
+
%% - Each SFTP session is a gen_server, managed by ssh_sftp_sup
|
|
8
|
+
%% - Single session = serial operations (no concurrent ops on same channel)
|
|
9
|
+
%% - Concurrent transfers = multiple sftp_sessions on the same conn
|
|
10
|
+
%% - Connection close → sftp_sup cleans up all sessions for that conn_id
|
|
11
|
+
|
|
12
|
+
-export([
|
|
13
|
+
start_link/2,
|
|
14
|
+
conn_id/1,
|
|
15
|
+
get_id/1,
|
|
16
|
+
rpc_list_dir/1,
|
|
17
|
+
rpc_download/1,
|
|
18
|
+
rpc_upload/1,
|
|
19
|
+
rpc_mkdir/1,
|
|
20
|
+
rpc_remove/1,
|
|
21
|
+
rpc_stat/1
|
|
22
|
+
]).
|
|
23
|
+
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
|
|
24
|
+
|
|
25
|
+
-include("ssh_ipc.hrl").
|
|
26
|
+
-include_lib("kernel/include/file.hrl").
|
|
27
|
+
|
|
28
|
+
%% ---------- Public API ----------
|
|
29
|
+
|
|
30
|
+
%% @doc Start an SFTP session on an existing SSH connection.
|
|
31
|
+
start_link(ConnId, SshRef) ->
|
|
32
|
+
gen_server:start_link(?MODULE, [ConnId, SshRef], []).
|
|
33
|
+
|
|
34
|
+
%% @doc Get the connection ID for this session.
|
|
35
|
+
conn_id(Pid) ->
|
|
36
|
+
gen_server:call(Pid, conn_id).
|
|
37
|
+
|
|
38
|
+
%% @doc Get the sftp_id for this session.
|
|
39
|
+
get_id(Pid) ->
|
|
40
|
+
gen_server:call(Pid, get_id).
|
|
41
|
+
|
|
42
|
+
%% @doc RPC handler for sftp.list_dir
|
|
43
|
+
rpc_list_dir(#{<<"sftp_id">> := SftpId, <<"path">> := Path}) ->
|
|
44
|
+
case find_by_sftp_id(SftpId) of
|
|
45
|
+
{ok, Pid} -> gen_server:call(Pid, {list_dir, Path});
|
|
46
|
+
{error, not_found} -> {error, sftp_not_found}
|
|
47
|
+
end.
|
|
48
|
+
|
|
49
|
+
%% @doc RPC handler for sftp.download
|
|
50
|
+
rpc_download(#{<<"sftp_id">> := SftpId, <<"remote">> := Remote, <<"local">> := Local}) ->
|
|
51
|
+
case find_by_sftp_id(SftpId) of
|
|
52
|
+
{ok, Pid} -> gen_server:call(Pid, {download, Remote, Local}, infinity);
|
|
53
|
+
{error, not_found} -> {error, sftp_not_found}
|
|
54
|
+
end.
|
|
55
|
+
|
|
56
|
+
%% @doc RPC handler for sftp.upload
|
|
57
|
+
rpc_upload(#{<<"sftp_id">> := SftpId, <<"local">> := Local, <<"remote">> := Remote}) ->
|
|
58
|
+
case find_by_sftp_id(SftpId) of
|
|
59
|
+
{ok, Pid} -> gen_server:call(Pid, {upload, Local, Remote}, infinity);
|
|
60
|
+
{error, not_found} -> {error, sftp_not_found}
|
|
61
|
+
end.
|
|
62
|
+
|
|
63
|
+
%% @doc RPC handler for sftp.mkdir
|
|
64
|
+
rpc_mkdir(#{<<"sftp_id">> := SftpId, <<"path">> := Path}) ->
|
|
65
|
+
case find_by_sftp_id(SftpId) of
|
|
66
|
+
{ok, Pid} -> gen_server:call(Pid, {mkdir, Path});
|
|
67
|
+
{error, not_found} -> {error, sftp_not_found}
|
|
68
|
+
end.
|
|
69
|
+
|
|
70
|
+
%% @doc RPC handler for sftp.remove
|
|
71
|
+
rpc_remove(#{<<"sftp_id">> := SftpId, <<"path">> := Path}) ->
|
|
72
|
+
case find_by_sftp_id(SftpId) of
|
|
73
|
+
{ok, Pid} -> gen_server:call(Pid, {remove, Path});
|
|
74
|
+
{error, not_found} -> {error, sftp_not_found}
|
|
75
|
+
end.
|
|
76
|
+
|
|
77
|
+
%% @doc RPC handler for sftp.stat
|
|
78
|
+
rpc_stat(#{<<"sftp_id">> := SftpId, <<"path">> := Path}) ->
|
|
79
|
+
case find_by_sftp_id(SftpId) of
|
|
80
|
+
{ok, Pid} -> gen_server:call(Pid, {stat, Path});
|
|
81
|
+
{error, not_found} -> {error, sftp_not_found}
|
|
82
|
+
end.
|
|
83
|
+
|
|
84
|
+
%% ---------- gen_server callbacks ----------
|
|
85
|
+
|
|
86
|
+
init([ConnId, SshRef]) ->
|
|
87
|
+
Id = ssh_codec:gen_sftp_id(ConnId),
|
|
88
|
+
case ssh_sftp:start_channel(SshRef, [{window, 10}, {packet, 32768}]) of
|
|
89
|
+
{ok, SftpPid} ->
|
|
90
|
+
{ok, #sftp{id = Id, conn_id = ConnId, ssh_ref = SshRef, sftp_pid = SftpPid}};
|
|
91
|
+
{error, Reason} ->
|
|
92
|
+
{stop, Reason}
|
|
93
|
+
end.
|
|
94
|
+
|
|
95
|
+
handle_call(conn_id, _From, #sftp{conn_id = ConnId} = S) ->
|
|
96
|
+
{reply, ConnId, S};
|
|
97
|
+
|
|
98
|
+
handle_call(get_id, _From, #sftp{id = Id} = S) ->
|
|
99
|
+
{reply, Id, S};
|
|
100
|
+
|
|
101
|
+
handle_call({list_dir, Path}, _From, #sftp{sftp_pid = P} = S) ->
|
|
102
|
+
case ssh_sftp:read_file_info_all(P, binary_to_list(Path)) of
|
|
103
|
+
{ok, Entries} ->
|
|
104
|
+
{reply, {ok, format_entries(Entries)}, S};
|
|
105
|
+
{error, E} ->
|
|
106
|
+
{reply, {error, E}, S}
|
|
107
|
+
end;
|
|
108
|
+
|
|
109
|
+
handle_call({download, Remote, Local}, _From, #sftp{sftp_pid = P} = S) ->
|
|
110
|
+
Result = do_download(P, binary_to_list(Remote), binary_to_list(Local), S),
|
|
111
|
+
{reply, Result, S};
|
|
112
|
+
|
|
113
|
+
handle_call({upload, Local, Remote}, _From, #sftp{sftp_pid = P} = S) ->
|
|
114
|
+
Result = do_upload(P, binary_to_list(Local), binary_to_list(Remote), S),
|
|
115
|
+
{reply, Result, S};
|
|
116
|
+
|
|
117
|
+
handle_call({mkdir, Path}, _From, #sftp{sftp_pid = P} = S) ->
|
|
118
|
+
Result = ssh_sftp:make_dir(P, binary_to_list(Path)),
|
|
119
|
+
{reply, Result, S};
|
|
120
|
+
|
|
121
|
+
handle_call({remove, Path}, _From, #sftp{sftp_pid = P} = S) ->
|
|
122
|
+
Result = ssh_sftp:delete(P, binary_to_list(Path)),
|
|
123
|
+
{reply, Result, S};
|
|
124
|
+
|
|
125
|
+
handle_call({stat, Path}, _From, #sftp{sftp_pid = P} = S) ->
|
|
126
|
+
case ssh_sftp:read_file_info(P, binary_to_list(Path)) of
|
|
127
|
+
{ok, #file_info{size = Size, mtime = Mtime, mode = Mode, type = Type}} ->
|
|
128
|
+
{reply, {ok, #{size => Size, mtime => Mtime, perms => Mode,
|
|
129
|
+
type => atom_to_binary(Type, utf8)}}, S};
|
|
130
|
+
{error, E} ->
|
|
131
|
+
{reply, {error, E}, S}
|
|
132
|
+
end;
|
|
133
|
+
|
|
134
|
+
handle_call(_Req, _From, S) ->
|
|
135
|
+
{reply, {error, not_implemented}, S}.
|
|
136
|
+
|
|
137
|
+
handle_cast(_Msg, S) ->
|
|
138
|
+
{noreply, S}.
|
|
139
|
+
|
|
140
|
+
handle_info(_Info, S) ->
|
|
141
|
+
{noreply, S}.
|
|
142
|
+
|
|
143
|
+
terminate(_Reason, #sftp{sftp_pid = P}) when is_pid(P) ->
|
|
144
|
+
_ = (catch ssh_sftp:stop_channel(P)),
|
|
145
|
+
ok;
|
|
146
|
+
terminate(_Reason, _S) ->
|
|
147
|
+
ok.
|
|
148
|
+
|
|
149
|
+
%% ---------- Internal ----------
|
|
150
|
+
|
|
151
|
+
%% @doc Format file_info entries for JSON response.
|
|
152
|
+
format_entries(Entries) ->
|
|
153
|
+
[format_entry(E) || E <- Entries].
|
|
154
|
+
|
|
155
|
+
format_entry({Name, #file_info{size = Size, mtime = Mtime, mode = Mode, type = Type}}) ->
|
|
156
|
+
#{
|
|
157
|
+
name => Name,
|
|
158
|
+
type => atom_to_binary(Type, utf8),
|
|
159
|
+
size => Size,
|
|
160
|
+
mtime => Mtime,
|
|
161
|
+
perms => Mode
|
|
162
|
+
}.
|
|
163
|
+
|
|
164
|
+
%% @doc Download a file from remote to local.
|
|
165
|
+
do_download(SftpPid, RemotePath, LocalPath, SftpState) ->
|
|
166
|
+
case ssh_sftp:read_file_info(SftpPid, RemotePath) of
|
|
167
|
+
{ok, #file_info{size = TotalSize, type = regular}} ->
|
|
168
|
+
case file:open(LocalPath, [write, binary]) of
|
|
169
|
+
{ok, LocalFile} ->
|
|
170
|
+
case ssh_sftp:open(SftpPid, RemotePath, [read, binary]) of
|
|
171
|
+
{ok, Handle} ->
|
|
172
|
+
Transferred = stream_download(SftpPid, Handle, LocalFile, 0, TotalSize, SftpState),
|
|
173
|
+
ssh_sftp:close(SftpPid, Handle),
|
|
174
|
+
file:close(LocalFile),
|
|
175
|
+
{ok, #{transferred => Transferred, total => TotalSize}};
|
|
176
|
+
{error, E} ->
|
|
177
|
+
file:close(LocalFile),
|
|
178
|
+
{error, E}
|
|
179
|
+
end;
|
|
180
|
+
{error, E} ->
|
|
181
|
+
{error, E}
|
|
182
|
+
end;
|
|
183
|
+
{error, E} ->
|
|
184
|
+
{error, E}
|
|
185
|
+
end.
|
|
186
|
+
|
|
187
|
+
stream_download(SftpPid, Handle, LocalFile, Transferred, Total, SftpState) ->
|
|
188
|
+
case ssh_sftp:read(SftpPid, Handle, 32768) of
|
|
189
|
+
{ok, Data} ->
|
|
190
|
+
ok = file:write(LocalFile, Data),
|
|
191
|
+
NewTransferred = Transferred + byte_size(Data),
|
|
192
|
+
push_progress(SftpState, NewTransferred, Total),
|
|
193
|
+
case byte_size(Data) < 32768 of
|
|
194
|
+
true -> NewTransferred;
|
|
195
|
+
false -> stream_download(SftpPid, Handle, LocalFile, NewTransferred, Total, SftpState)
|
|
196
|
+
end;
|
|
197
|
+
eof ->
|
|
198
|
+
Transferred
|
|
199
|
+
end.
|
|
200
|
+
|
|
201
|
+
%% @doc Upload a file from local to remote.
|
|
202
|
+
do_upload(SftpPid, LocalPath, RemotePath, SftpState) ->
|
|
203
|
+
case file:read_file_info(LocalPath) of
|
|
204
|
+
{ok, #file_info{size = TotalSize}} ->
|
|
205
|
+
case file:open(LocalPath, [read, binary]) of
|
|
206
|
+
{ok, LocalFile} ->
|
|
207
|
+
case ssh_sftp:open(SftpPid, RemotePath, [write, binary]) of
|
|
208
|
+
{ok, Handle} ->
|
|
209
|
+
Transferred = stream_upload(SftpPid, Handle, LocalFile, 0, TotalSize, SftpState),
|
|
210
|
+
ssh_sftp:close(SftpPid, Handle),
|
|
211
|
+
file:close(LocalFile),
|
|
212
|
+
{ok, #{transferred => Transferred, total => TotalSize}};
|
|
213
|
+
{error, E} ->
|
|
214
|
+
file:close(LocalFile),
|
|
215
|
+
{error, E}
|
|
216
|
+
end;
|
|
217
|
+
{error, E} ->
|
|
218
|
+
{error, E}
|
|
219
|
+
end;
|
|
220
|
+
{error, E} ->
|
|
221
|
+
{error, E}
|
|
222
|
+
end.
|
|
223
|
+
|
|
224
|
+
stream_upload(SftpPid, Handle, LocalFile, Transferred, Total, SftpState) ->
|
|
225
|
+
case file:read(LocalFile, 32768) of
|
|
226
|
+
{ok, Data} ->
|
|
227
|
+
ok = ssh_sftp:write(SftpPid, Handle, Data),
|
|
228
|
+
NewTransferred = Transferred + byte_size(Data),
|
|
229
|
+
push_progress(SftpState, NewTransferred, Total),
|
|
230
|
+
stream_upload(SftpPid, Handle, LocalFile, NewTransferred, Total, SftpState);
|
|
231
|
+
eof ->
|
|
232
|
+
Transferred
|
|
233
|
+
end.
|
|
234
|
+
|
|
235
|
+
%% @doc Push a transfer progress notification to Ruby.
|
|
236
|
+
push_progress(#sftp{id = Id}, Transferred, Total) ->
|
|
237
|
+
ssh_ipc_gateway:push_event(<<"sftp.progress">>, #{
|
|
238
|
+
<<"sftp_id">> => Id,
|
|
239
|
+
<<"transferred">> => Transferred,
|
|
240
|
+
<<"total">> => Total,
|
|
241
|
+
<<"speed">> => 0
|
|
242
|
+
}).
|
|
243
|
+
|
|
244
|
+
%% @doc Find an SFTP session by sftp_id.
|
|
245
|
+
find_by_sftp_id(SftpId) ->
|
|
246
|
+
case ets:lookup(sftp_index, SftpId) of
|
|
247
|
+
[{_, Pid}] -> {ok, Pid};
|
|
248
|
+
[] -> {error, not_found}
|
|
249
|
+
end.
|
|
250
|
+
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
-module(ssh_sftp_sup).
|
|
2
|
+
-behaviour(supervisor).
|
|
3
|
+
|
|
4
|
+
-export([start_link/0, start_child/2, list_for_conn/1, rpc_open/1]).
|
|
5
|
+
-export([init/1]).
|
|
6
|
+
|
|
7
|
+
%% @doc Start the SFTP session pool supervisor (simple_one_for_one).
|
|
8
|
+
start_link() ->
|
|
9
|
+
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
|
|
10
|
+
|
|
11
|
+
%% @doc Start a new SFTP session worker.
|
|
12
|
+
%% ConnId identifies the parent connection; SshRef is the ssh connection reference.
|
|
13
|
+
%% Returns {ok, SftpId} | {error, Reason}.
|
|
14
|
+
start_child(ConnId, SshRef) ->
|
|
15
|
+
case supervisor:start_child(?MODULE, [ConnId, SshRef]) of
|
|
16
|
+
{ok, Pid} ->
|
|
17
|
+
%% Retrieve the sftp_id generated by the session worker
|
|
18
|
+
SftpId = ssh_sftp_session:get_id(Pid),
|
|
19
|
+
ets:insert(sftp_index, {SftpId, Pid}),
|
|
20
|
+
{ok, SftpId};
|
|
21
|
+
{error, Reason} ->
|
|
22
|
+
{error, Reason}
|
|
23
|
+
end.
|
|
24
|
+
|
|
25
|
+
%% @doc List all SFTP sessions for a given connection.
|
|
26
|
+
list_for_conn(ConnId) ->
|
|
27
|
+
[Pid || {_, Pid, _, _} <- supervisor:which_children(?MODULE),
|
|
28
|
+
ssh_sftp_session:conn_id(Pid) =:= ConnId].
|
|
29
|
+
|
|
30
|
+
%% @doc RPC handler for sftp.open
|
|
31
|
+
rpc_open(#{<<"conn_id">> := ConnId}) ->
|
|
32
|
+
case find_conn_worker(ConnId) of
|
|
33
|
+
{ok, Pid} ->
|
|
34
|
+
SshRef = ssh_conn_worker:get_ssh_ref(Pid),
|
|
35
|
+
start_child(ConnId, SshRef);
|
|
36
|
+
{error, not_found} ->
|
|
37
|
+
{error, conn_not_found}
|
|
38
|
+
end.
|
|
39
|
+
|
|
40
|
+
%% @doc simple_one_for_one, intensity 20/60s, temporary workers.
|
|
41
|
+
init([]) ->
|
|
42
|
+
SupFlags = #{
|
|
43
|
+
strategy => simple_one_for_one,
|
|
44
|
+
intensity => 20,
|
|
45
|
+
period => 60
|
|
46
|
+
},
|
|
47
|
+
ChildSpec = #{
|
|
48
|
+
id => ssh_sftp_session,
|
|
49
|
+
start => {ssh_sftp_session, start_link, []},
|
|
50
|
+
restart => temporary,
|
|
51
|
+
shutdown => 5000,
|
|
52
|
+
type => worker,
|
|
53
|
+
modules => [ssh_sftp_session]
|
|
54
|
+
},
|
|
55
|
+
{ok, {SupFlags, [ChildSpec]}}.
|
|
56
|
+
|
|
57
|
+
find_conn_worker(ConnId) ->
|
|
58
|
+
case [Pid || Pid <- ssh_conn_sup:all_workers(),
|
|
59
|
+
ssh_conn_worker:conn_id(Pid) =:= ConnId] of
|
|
60
|
+
[Pid | _] -> {ok, Pid};
|
|
61
|
+
[] -> {error, not_found}
|
|
62
|
+
end.
|