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