1 /*
2 * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
3 * Copyright (C) 2008-2009, Google Inc.
4 * Copyright (C) 2009, Google, Inc.
5 * Copyright (C) 2009, JetBrains s.r.o.
6 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
7 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
8 * and other copyright owners as documented in the project's IP log.
9 *
10 * This program and the accompanying materials are made available
11 * under the terms of the Eclipse Distribution License v1.0 which
12 * accompanies this distribution, is reproduced below, and is
13 * available at http://www.eclipse.org/org/documents/edl-v10.php
14 *
15 * All rights reserved.
16 *
17 * Redistribution and use in source and binary forms, with or
18 * without modification, are permitted provided that the following
19 * conditions are met:
20 *
21 * - Redistributions of source code must retain the above copyright
22 * notice, this list of conditions and the following disclaimer.
23 *
24 * - Redistributions in binary form must reproduce the above
25 * copyright notice, this list of conditions and the following
26 * disclaimer in the documentation and/or other materials provided
27 * with the distribution.
28 *
29 * - Neither the name of the Eclipse Foundation, Inc. nor the
30 * names of its contributors may be used to endorse or promote
31 * products derived from this software without specific prior
32 * written permission.
33 *
34 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
35 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
36 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
38 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
39 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
43 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
46 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
47 */
48
49 package org.eclipse.jgit.transport;
50
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.OutputStream;
54 import java.io.PipedInputStream;
55 import java.io.PipedOutputStream;
56
57 import org.eclipse.jgit.errors.TransportException;
58 import org.eclipse.jgit.internal.JGitText;
59 import org.eclipse.jgit.util.io.StreamCopyThread;
60
61 import com.jcraft.jsch.Channel;
62 import com.jcraft.jsch.ChannelExec;
63 import com.jcraft.jsch.JSchException;
64 import com.jcraft.jsch.Session;
65
66 /**
67 * Run remote commands using Jsch.
68 * <p>
69 * This class is the default session implementation using Jsch. Note that
70 * {@link JschConfigSessionFactory} is used to create the actual session passed
71 * to the constructor.
72 */
73 public class JschSession implements RemoteSession {
74 private final Session sock;
75 private final URIish uri;
76
77 /**
78 * Create a new session object by passing the real Jsch session and the URI
79 * information.
80 *
81 * @param session
82 * the real Jsch session created elsewhere.
83 * @param uri
84 * the URI information for the remote connection
85 */
86 public JschSession(final Session session, URIish uri) {
87 sock = session;
88 this.uri = uri;
89 }
90
91 public Process exec(String command, int timeout) throws IOException {
92 return new JschProcess(command, timeout);
93 }
94
95 public void disconnect() {
96 if (sock.isConnected())
97 sock.disconnect();
98 }
99
100 /**
101 * A kludge to allow {@link TransportSftp} to get an Sftp channel from Jsch.
102 * Ideally, this method would be generic, which would require implementing
103 * generic Sftp channel operations in the RemoteSession class.
104 *
105 * @return a channel suitable for Sftp operations.
106 * @throws JSchException
107 * on problems getting the channel.
108 */
109 public Channel getSftpChannel() throws JSchException {
110 return sock.openChannel("sftp"); //$NON-NLS-1$
111 }
112
113 /**
114 * Implementation of Process for running a single command using Jsch.
115 * <p>
116 * Uses the Jsch session to do actual command execution and manage the
117 * execution.
118 */
119 private class JschProcess extends Process {
120 private ChannelExec channel;
121
122 private final int timeout;
123
124 private InputStream inputStream;
125
126 private OutputStream outputStream;
127
128 private InputStream errStream;
129
130 /**
131 * Opens a channel on the session ("sock") for executing the given
132 * command, opens streams, and starts command execution.
133 *
134 * @param commandName
135 * the command to execute
136 * @param tms
137 * the timeout value, in seconds, for the command.
138 * @throws TransportException
139 * on problems opening a channel or connecting to the remote
140 * host
141 * @throws IOException
142 * on problems opening streams
143 */
144 private JschProcess(final String commandName, int tms)
145 throws TransportException, IOException {
146 timeout = tms;
147 try {
148 channel = (ChannelExec) sock.openChannel("exec"); //$NON-NLS-1$
149 channel.setCommand(commandName);
150 setupStreams();
151 channel.connect(timeout > 0 ? timeout * 1000 : 0);
152 if (!channel.isConnected())
153 throw new TransportException(uri,
154 JGitText.get().connectionFailed);
155 } catch (JSchException e) {
156 throw new TransportException(uri, e.getMessage(), e);
157 }
158 }
159
160 private void setupStreams() throws IOException {
161 inputStream = channel.getInputStream();
162
163 // JSch won't let us interrupt writes when we use our InterruptTimer
164 // to break out of a long-running write operation. To work around
165 // that we spawn a background thread to shuttle data through a pipe,
166 // as we can issue an interrupted write out of that. Its slower, so
167 // we only use this route if there is a timeout.
168 final OutputStream out = channel.getOutputStream();
169 if (timeout <= 0) {
170 outputStream = out;
171 } else {
172 final PipedInputStream pipeIn = new PipedInputStream();
173 final StreamCopyThread copier = new StreamCopyThread(pipeIn,
174 out);
175 final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) {
176 @Override
177 public void flush() throws IOException {
178 super.flush();
179 copier.flush();
180 }
181
182 @Override
183 public void close() throws IOException {
184 super.close();
185 try {
186 copier.join(timeout * 1000);
187 } catch (InterruptedException e) {
188 // Just wake early, the thread will terminate
189 // anyway.
190 }
191 }
192 };
193 copier.start();
194 outputStream = pipeOut;
195 }
196
197 errStream = channel.getErrStream();
198 }
199
200 @Override
201 public InputStream getInputStream() {
202 return inputStream;
203 }
204
205 @Override
206 public OutputStream getOutputStream() {
207 return outputStream;
208 }
209
210 @Override
211 public InputStream getErrorStream() {
212 return errStream;
213 }
214
215 @Override
216 public int exitValue() {
217 if (isRunning())
218 throw new IllegalStateException();
219 return channel.getExitStatus();
220 }
221
222 private boolean isRunning() {
223 return channel.getExitStatus() < 0 && channel.isConnected();
224 }
225
226 @Override
227 public void destroy() {
228 if (channel.isConnected())
229 channel.disconnect();
230 }
231
232 @Override
233 public int waitFor() throws InterruptedException {
234 while (isRunning())
235 Thread.sleep(100);
236 return exitValue();
237 }
238 }
239 }