1 package org.apache.maven.wagon.providers.ssh.jsch;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.EOFException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26
27 import org.apache.maven.wagon.CommandExecutionException;
28 import org.apache.maven.wagon.InputData;
29 import org.apache.maven.wagon.OutputData;
30 import org.apache.maven.wagon.ResourceDoesNotExistException;
31 import org.apache.maven.wagon.TransferFailedException;
32 import org.apache.maven.wagon.events.TransferEvent;
33 import org.apache.maven.wagon.providers.ssh.ScpHelper;
34 import org.apache.maven.wagon.repository.RepositoryPermissions;
35 import org.apache.maven.wagon.resource.Resource;
36
37 import com.jcraft.jsch.ChannelExec;
38 import com.jcraft.jsch.JSchException;
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 public class ScpWagon
58 extends AbstractJschWagon
59 {
60 private static final char COPY_START_CHAR = 'C';
61
62 private static final char ACK_SEPARATOR = ' ';
63
64 private static final String END_OF_FILES_MSG = "E\n";
65
66 private static final int LINE_BUFFER_SIZE = 8192;
67
68 private static final byte LF = '\n';
69
70 private ChannelExec channel;
71
72 private InputStream channelInputStream;
73
74 private OutputStream channelOutputStream;
75
76 private void setFileGroup( RepositoryPermissions permissions, String basedir, Resource resource )
77 throws CommandExecutionException
78 {
79 if ( permissions != null && permissions.getGroup() != null )
80 {
81 executeCommand( "chgrp -f " + permissions.getGroup() + " " + getPath( basedir, resource.getName() ) );
82 }
83 }
84
85 protected void cleanupPutTransfer( Resource resource )
86 {
87 if ( channel != null )
88 {
89 channel.disconnect();
90 channel = null;
91 }
92 }
93
94 protected void finishPutTransfer( Resource resource, InputStream input, OutputStream output )
95 throws TransferFailedException
96 {
97 try
98 {
99 sendEom( output );
100
101 checkAck( channelInputStream );
102
103
104 output.write( END_OF_FILES_MSG.getBytes() );
105 output.flush();
106 }
107 catch ( IOException e )
108 {
109 handleIOException( resource, e );
110 }
111
112 String basedir = getRepository().getBasedir();
113 try
114 {
115 setFileGroup( getRepository().getPermissions(), basedir, resource );
116 }
117 catch ( CommandExecutionException e )
118 {
119 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
120
121 throw new TransferFailedException( e.getMessage(), e );
122 }
123 }
124
125 private void checkAck( InputStream in )
126 throws IOException
127 {
128 int code = in.read();
129 if ( code == -1 )
130 {
131 throw new IOException( "Unexpected end of data" );
132 }
133 else if ( code == 1 )
134 {
135 String line = readLine( in );
136
137 throw new IOException( "SCP terminated with error: '" + line + "'" );
138 }
139 else if ( code == 2 )
140 {
141 throw new IOException( "SCP terminated with error (code: " + code + ")" );
142 }
143 else if ( code != 0 )
144 {
145 throw new IOException( "SCP terminated with unknown error code" );
146 }
147 }
148
149 protected void finishGetTransfer( Resource resource, InputStream input, OutputStream output )
150 throws TransferFailedException
151 {
152 try
153 {
154 checkAck( input );
155
156 sendEom( channelOutputStream );
157 }
158 catch ( IOException e )
159 {
160 handleGetException( resource, e );
161 }
162 }
163
164 protected void cleanupGetTransfer( Resource resource )
165 {
166 if ( channel != null )
167 {
168 channel.disconnect();
169 }
170 }
171
172 protected void getTransfer( Resource resource, OutputStream output, InputStream input, boolean closeInput,
173 int maxSize )
174 throws TransferFailedException
175 {
176 super.getTransfer( resource, output, input, closeInput, (int) resource.getContentLength() );
177 }
178
179 protected String readLine( InputStream in )
180 throws IOException
181 {
182 StringBuffer sb = new StringBuffer();
183
184 while ( true )
185 {
186 if ( sb.length() > LINE_BUFFER_SIZE )
187 {
188 throw new IOException( "Remote server sent a too long line" );
189 }
190
191 int c = in.read();
192
193 if ( c < 0 )
194 {
195 throw new IOException( "Remote connection terminated unexpectedly." );
196 }
197
198 if ( c == LF )
199 {
200 break;
201 }
202
203 sb.append( (char) c );
204 }
205 return sb.toString();
206 }
207
208 protected static void sendEom( OutputStream out )
209 throws IOException
210 {
211 out.write( 0 );
212
213 out.flush();
214 }
215
216 public void fillInputData( InputData inputData )
217 throws TransferFailedException, ResourceDoesNotExistException
218 {
219 Resource resource = inputData.getResource();
220
221 String path = getPath( getRepository().getBasedir(), resource.getName() );
222 String cmd = "scp -p -f " + path;
223
224 fireTransferDebug( "Executing command: " + cmd );
225
226 try
227 {
228 channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
229
230 channel.setCommand( cmd );
231
232
233 channelOutputStream = channel.getOutputStream();
234
235 InputStream in = channel.getInputStream();
236 inputData.setInputStream( in );
237
238 channel.connect();
239
240 sendEom( channelOutputStream );
241
242 int exitCode = in.read();
243
244 if ( exitCode == 'T' )
245 {
246 String line = readLine( in );
247
248 String[] times = line.split( " " );
249
250 resource.setLastModified( Long.valueOf( times[0] ).longValue() * 1000 );
251
252 sendEom( channelOutputStream );
253
254 exitCode = in.read();
255 }
256
257 String line = readLine( in );
258
259 if ( exitCode != COPY_START_CHAR )
260 {
261 if ( exitCode == 1 && line.indexOf( "No such file or directory" ) != -1 )
262 {
263 throw new ResourceDoesNotExistException( line );
264 }
265 else
266 {
267 throw new IOException( "Exit code: " + exitCode + " - " + line );
268 }
269 }
270
271 if ( line == null )
272 {
273 throw new EOFException( "Unexpected end of data" );
274 }
275
276 String perms = line.substring( 0, 4 );
277 fireTransferDebug( "Remote file permissions: " + perms );
278
279 if ( line.charAt( 4 ) != ACK_SEPARATOR && line.charAt( 5 ) != ACK_SEPARATOR )
280 {
281 throw new IOException( "Invalid transfer header: " + line );
282 }
283
284 int index = line.indexOf( ACK_SEPARATOR, 5 );
285 if ( index < 0 )
286 {
287 throw new IOException( "Invalid transfer header: " + line );
288 }
289
290 int filesize = Integer.valueOf( line.substring( 5, index ) ).intValue();
291 fireTransferDebug( "Remote file size: " + filesize );
292
293 resource.setContentLength( filesize );
294
295 String filename = line.substring( index + 1 );
296 fireTransferDebug( "Remote filename: " + filename );
297
298 sendEom( channelOutputStream );
299 }
300 catch ( JSchException e )
301 {
302 handleGetException( resource, e );
303 }
304 catch ( IOException e )
305 {
306 handleGetException( resource, e );
307 }
308 }
309
310 public void fillOutputData( OutputData outputData )
311 throws TransferFailedException
312 {
313 Resource resource = outputData.getResource();
314
315 String basedir = getRepository().getBasedir();
316
317 String path = getPath( basedir, resource.getName() );
318
319 String dir = ScpHelper.getResourceDirectory( resource.getName() );
320
321 try
322 {
323 sshTool.createRemoteDirectories( getPath( basedir, dir ), getRepository().getPermissions() );
324 }
325 catch ( CommandExecutionException e )
326 {
327 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
328
329 throw new TransferFailedException( e.getMessage(), e );
330 }
331
332 String octalMode = getOctalMode( getRepository().getPermissions() );
333
334
335 String command = "scp";
336 if ( octalMode != null )
337 {
338 command += " -p";
339 }
340 command += " -t \"" + path + "\"";
341
342 fireTransferDebug( "Executing command: " + command );
343
344 String resourceName = resource.getName();
345
346 OutputStream out = null;
347 try
348 {
349 channel = (ChannelExec) session.openChannel( EXEC_CHANNEL );
350
351 channel.setCommand( command );
352
353
354 out = channel.getOutputStream();
355 outputData.setOutputStream( out );
356
357 channelInputStream = channel.getInputStream();
358
359 channel.connect();
360
361 checkAck( channelInputStream );
362
363
364 long filesize = resource.getContentLength();
365
366 String mode = octalMode == null ? "0644" : octalMode;
367 command = "C" + mode + " " + filesize + " ";
368
369 if ( resourceName.lastIndexOf( ScpHelper.PATH_SEPARATOR ) > 0 )
370 {
371 command += resourceName.substring( resourceName.lastIndexOf( ScpHelper.PATH_SEPARATOR ) + 1 );
372 }
373 else
374 {
375 command += resourceName;
376 }
377
378 command += "\n";
379
380 out.write( command.getBytes() );
381
382 out.flush();
383
384 checkAck( channelInputStream );
385 }
386 catch ( JSchException e )
387 {
388 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
389
390 String msg = "Error occurred while deploying '" + resourceName + "' to remote repository: "
391 + getRepository().getUrl() + ": " + e.getMessage();
392
393 throw new TransferFailedException( msg, e );
394 }
395 catch ( IOException e )
396 {
397 handleIOException( resource, e );
398 }
399 }
400
401 private void handleIOException( Resource resource, IOException e )
402 throws TransferFailedException
403 {
404 if ( e.getMessage().indexOf( "set mode: Operation not permitted" ) >= 0 )
405 {
406 fireTransferDebug( e.getMessage() );
407 }
408 else
409 {
410 fireTransferError( resource, e, TransferEvent.REQUEST_PUT );
411
412 String msg = "Error occurred while deploying '" + resource.getName() + "' to remote repository: "
413 + getRepository().getUrl() + ": " + e.getMessage();
414
415 throw new TransferFailedException( msg, e );
416 }
417 }
418
419 public String getOctalMode( RepositoryPermissions permissions )
420 {
421 String mode = null;
422 if ( permissions != null && permissions.getFileMode() != null )
423 {
424 if ( permissions.getFileMode().matches( "[0-9]{3,4}" ) )
425 {
426 mode = permissions.getFileMode();
427
428 if ( mode.length() == 3 )
429 {
430 mode = "0" + mode;
431 }
432 }
433 else
434 {
435
436
437 fireSessionDebug( "Not using non-octal permissions: " + permissions.getFileMode() );
438 }
439 }
440 return mode;
441 }
442 }