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.BufferedOutputStream;
52 import java.io.IOException;
53 import java.io.InputStream;
54 import java.io.OutputStream;
55
56 import org.eclipse.jgit.errors.TransportException;
57 import org.eclipse.jgit.internal.JGitText;
58 import org.eclipse.jgit.util.io.IsolatedOutputStream;
59
60 import com.jcraft.jsch.Channel;
61 import com.jcraft.jsch.ChannelExec;
62 import com.jcraft.jsch.JSchException;
63 import com.jcraft.jsch.Session;
64
65 /**
66 * Run remote commands using Jsch.
67 * <p>
68 * This class is the default session implementation using Jsch. Note that
69 * {@link org.eclipse.jgit.transport.JschConfigSessionFactory} is used to create
70 * the actual session passed to the constructor.
71 */
72 public class JschSession implements RemoteSession {
73 final Session sock;
74 final URIish uri;
75
76 /**
77 * Create a new session object by passing the real Jsch session and the URI
78 * information.
79 *
80 * @param session
81 * the real Jsch session created elsewhere.
82 * @param uri
83 * the URI information for the remote connection
84 */
85 public JschSession(Session session, URIish uri) {
86 sock = session;
87 this.uri = uri;
88 }
89
90 /** {@inheritDoc} */
91 @Override
92 public Process exec(String command, int timeout) throws IOException {
93 return new JschProcess(command, timeout);
94 }
95
96 /** {@inheritDoc} */
97 @Override
98 public void disconnect() {
99 if (sock.isConnected())
100 sock.disconnect();
101 }
102
103 /**
104 * A kludge to allow {@link org.eclipse.jgit.transport.TransportSftp} to get
105 * an Sftp channel from Jsch. Ideally, this method would be generic, which
106 * would require implementing generic Sftp channel operations in the
107 * RemoteSession class.
108 *
109 * @return a channel suitable for Sftp operations.
110 * @throws com.jcraft.jsch.JSchException
111 * on problems getting the channel.
112 */
113 public Channel getSftpChannel() throws JSchException {
114 return sock.openChannel("sftp"); //$NON-NLS-1$
115 }
116
117 /**
118 * Implementation of Process for running a single command using Jsch.
119 * <p>
120 * Uses the Jsch session to do actual command execution and manage the
121 * execution.
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 * Opens a channel on the session ("sock") for executing the given
136 * command, opens streams, and starts command execution.
137 *
138 * @param commandName
139 * the command to execute
140 * @param tms
141 * the timeout value, in seconds, for the command.
142 * @throws TransportException
143 * on problems opening a channel or connecting to the remote
144 * host
145 * @throws IOException
146 * on problems opening streams
147 */
148 JschProcess(String commandName, int tms)
149 throws TransportException, IOException {
150 timeout = tms;
151 try {
152 channel = (ChannelExec) sock.openChannel("exec"); //$NON-NLS-1$
153 channel.setCommand(commandName);
154 setupStreams();
155 channel.connect(timeout > 0 ? timeout * 1000 : 0);
156 if (!channel.isConnected()) {
157 closeOutputStream();
158 throw new TransportException(uri,
159 JGitText.get().connectionFailed);
160 }
161 } catch (JSchException e) {
162 closeOutputStream();
163 throw new TransportException(uri, e.getMessage(), e);
164 }
165 }
166
167 private void closeOutputStream() {
168 if (outputStream != null) {
169 try {
170 outputStream.close();
171 } catch (IOException ioe) {
172 // ignore
173 }
174 }
175 }
176
177 private void setupStreams() throws IOException {
178 inputStream = channel.getInputStream();
179
180 // JSch won't let us interrupt writes when we use our InterruptTimer
181 // to break out of a long-running write operation. To work around
182 // that we spawn a background thread to shuttle data through a pipe,
183 // as we can issue an interrupted write out of that. Its slower, so
184 // we only use this route if there is a timeout.
185 OutputStream out = channel.getOutputStream();
186 if (timeout <= 0) {
187 outputStream = out;
188 } else {
189 IsolatedOutputStream i = new IsolatedOutputStream(out);
190 outputStream = new BufferedOutputStream(i, 16 * 1024);
191 }
192
193 errStream = channel.getErrStream();
194 }
195
196 @Override
197 public InputStream getInputStream() {
198 return inputStream;
199 }
200
201 @Override
202 public OutputStream getOutputStream() {
203 return outputStream;
204 }
205
206 @Override
207 public InputStream getErrorStream() {
208 return errStream;
209 }
210
211 @Override
212 public int exitValue() {
213 if (isRunning())
214 throw new IllegalStateException();
215 return channel.getExitStatus();
216 }
217
218 private boolean isRunning() {
219 return channel.getExitStatus() < 0 && channel.isConnected();
220 }
221
222 @Override
223 public void destroy() {
224 if (channel.isConnected())
225 channel.disconnect();
226 closeOutputStream();
227 }
228
229 @Override
230 public int waitFor() throws InterruptedException {
231 while (isRunning())
232 Thread.sleep(100);
233 return exitValue();
234 }
235 }
236 }