1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.eclipse.jgit.transport.ssh.jsch;
17
18 import java.io.BufferedOutputStream;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.Collections;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.Callable;
28 import java.util.concurrent.TimeUnit;
29
30 import org.eclipse.jgit.errors.TransportException;
31 import org.eclipse.jgit.internal.transport.ssh.jsch.JSchText;
32 import org.eclipse.jgit.transport.FtpChannel;
33 import org.eclipse.jgit.transport.RemoteSession2;
34 import org.eclipse.jgit.transport.URIish;
35 import org.eclipse.jgit.util.io.IsolatedOutputStream;
36
37 import com.jcraft.jsch.Channel;
38 import com.jcraft.jsch.ChannelExec;
39 import com.jcraft.jsch.ChannelSftp;
40 import com.jcraft.jsch.JSchException;
41 import com.jcraft.jsch.Session;
42 import com.jcraft.jsch.SftpException;
43
44
45
46
47
48
49
50
51
52
53 public class JschSession implements RemoteSession2 {
54 final Session sock;
55 final URIish uri;
56
57
58
59
60
61
62
63
64
65
66 public JschSession(Session session, URIish uri) {
67 sock = session;
68 this.uri = uri;
69 }
70
71
72 @Override
73 public Process exec(String command, int timeout) throws IOException {
74 return exec(command, Collections.emptyMap(), timeout);
75 }
76
77
78 @Override
79 public Process exec(String command, Map<String, String> environment,
80 int timeout) throws IOException {
81 return new JschProcess(command, environment, timeout);
82 }
83
84
85 @Override
86 public void disconnect() {
87 if (sock.isConnected())
88 sock.disconnect();
89 }
90
91
92
93
94
95
96
97
98
99
100
101
102 @Deprecated
103 public Channel getSftpChannel() throws JSchException {
104 return sock.openChannel("sftp");
105 }
106
107
108
109
110
111
112 @Override
113 public FtpChannel getFtpChannel() {
114 return new JschFtpChannel();
115 }
116
117
118
119
120
121
122
123 private class JschProcess extends Process {
124 private ChannelExec channel;
125
126 final int timeout;
127
128 private InputStream inputStream;
129
130 private OutputStream outputStream;
131
132 private InputStream errStream;
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150 JschProcess(String commandName, Map<String, String> environment,
151 int tms) throws TransportException, IOException {
152 timeout = tms;
153 try {
154 channel = (ChannelExec) sock.openChannel("exec");
155 if (environment != null) {
156 for (Map.Entry<String, String> envVar : environment
157 .entrySet()) {
158 channel.setEnv(envVar.getKey(), envVar.getValue());
159 }
160 }
161 channel.setCommand(commandName);
162 setupStreams();
163 channel.connect(timeout > 0 ? timeout * 1000 : 0);
164 if (!channel.isConnected()) {
165 closeOutputStream();
166 throw new TransportException(uri,
167 JSchText.get().connectionFailed);
168 }
169 } catch (JSchException e) {
170 closeOutputStream();
171 throw new TransportException(uri, e.getMessage(), e);
172 }
173 }
174
175 private void closeOutputStream() {
176 if (outputStream != null) {
177 try {
178 outputStream.close();
179 } catch (IOException ioe) {
180
181 }
182 }
183 }
184
185 private void setupStreams() throws IOException {
186 inputStream = channel.getInputStream();
187
188
189
190
191
192
193 OutputStream out = channel.getOutputStream();
194 if (timeout <= 0) {
195 outputStream = out;
196 } else {
197 IsolatedOutputStream i = new IsolatedOutputStream(out);
198 outputStream = new BufferedOutputStream(i, 16 * 1024);
199 }
200
201 errStream = channel.getErrStream();
202 }
203
204 @Override
205 public InputStream getInputStream() {
206 return inputStream;
207 }
208
209 @Override
210 public OutputStream getOutputStream() {
211 return outputStream;
212 }
213
214 @Override
215 public InputStream getErrorStream() {
216 return errStream;
217 }
218
219 @Override
220 public int exitValue() {
221 if (isRunning())
222 throw new IllegalThreadStateException();
223 return channel.getExitStatus();
224 }
225
226 private boolean isRunning() {
227 return channel.getExitStatus() < 0 && channel.isConnected();
228 }
229
230 @Override
231 public void destroy() {
232 if (channel.isConnected())
233 channel.disconnect();
234 closeOutputStream();
235 }
236
237 @Override
238 public int waitFor() throws InterruptedException {
239 while (isRunning())
240 Thread.sleep(100);
241 return exitValue();
242 }
243 }
244
245 private class JschFtpChannel implements FtpChannel {
246
247 private ChannelSftp ftp;
248
249 @Override
250 public void connect(int timeout, TimeUnit unit) throws IOException {
251 try {
252 ftp = (ChannelSftp) sock.openChannel("sftp");
253 ftp.connect((int) unit.toMillis(timeout));
254 } catch (JSchException e) {
255 ftp = null;
256 throw new IOException(e.getLocalizedMessage(), e);
257 }
258 }
259
260 @Override
261 public void disconnect() {
262 ftp.disconnect();
263 ftp = null;
264 }
265
266 private <T> T map(Callable<T> op) throws IOException {
267 try {
268 return op.call();
269 } catch (Exception e) {
270 if (e instanceof SftpException) {
271 throw new FtpChannel.FtpException(e.getLocalizedMessage(),
272 ((SftpException) e).id, e);
273 }
274 throw new IOException(e.getLocalizedMessage(), e);
275 }
276 }
277
278 @Override
279 public boolean isConnected() {
280 return ftp != null && sock.isConnected();
281 }
282
283 @Override
284 public void cd(String path) throws IOException {
285 map(() -> {
286 ftp.cd(path);
287 return null;
288 });
289 }
290
291 @Override
292 public String pwd() throws IOException {
293 return map(() -> ftp.pwd());
294 }
295
296 @Override
297 public Collection<DirEntry> ls(String path) throws IOException {
298 return map(() -> {
299 List<DirEntry> result = new ArrayList<>();
300 for (Object e : ftp.ls(path)) {
301 ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) e;
302 result.add(new DirEntry() {
303
304 @Override
305 public String getFilename() {
306 return entry.getFilename();
307 }
308
309 @Override
310 public long getModifiedTime() {
311 return entry.getAttrs().getMTime();
312 }
313
314 @Override
315 public boolean isDirectory() {
316 return entry.getAttrs().isDir();
317 }
318 });
319 }
320 return result;
321 });
322 }
323
324 @Override
325 public void rmdir(String path) throws IOException {
326 map(() -> {
327 ftp.rm(path);
328 return null;
329 });
330 }
331
332 @Override
333 public void mkdir(String path) throws IOException {
334 map(() -> {
335 ftp.mkdir(path);
336 return null;
337 });
338 }
339
340 @Override
341 public InputStream get(String path) throws IOException {
342 return map(() -> ftp.get(path));
343 }
344
345 @Override
346 public OutputStream put(String path) throws IOException {
347 return map(() -> ftp.put(path));
348 }
349
350 @Override
351 public void rm(String path) throws IOException {
352 map(() -> {
353 ftp.rm(path);
354 return null;
355 });
356 }
357
358 @Override
359 public void rename(String from, String to) throws IOException {
360 map(() -> {
361
362
363
364 if (hasPosixRename()) {
365 ftp.rename(from, to);
366 } else if (!to.equals(from)) {
367
368
369
370
371
372 delete(to);
373 ftp.rename(from, to);
374 }
375 return null;
376 });
377 }
378
379
380
381
382
383
384
385
386
387
388
389
390 private boolean hasPosixRename() {
391 return "1".equals(ftp.getExtension("posix-rename@openssh.com"));
392 }
393 }
394 }