blob: bdd80f543bb250af4fca3313c1ad945c001fa442 [file] [log] [blame]
package org.apache.maven.wagon.providers.ssh;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import org.apache.sshd.common.util.DirectoryScanner;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;
import org.apache.sshd.server.FileSystemAware;
import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.SshFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
/**
* This commands provide SCP support on both server and client side.
* Permissions and preservation of access / modification times on files
* are not supported.
* olamy : copy of a class from mina for changing return codes in case of file not found 1 warning instead of 2
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class ScpCommand
implements Command, Runnable, FileSystemAware
{
protected static final Logger LOG = LoggerFactory.getLogger( ScpCommand.class );
protected static final int OK = 0;
protected static final int WARNING = 1;
protected static final int ERROR = 2;
protected String name;
protected boolean optR;
protected boolean optT;
protected boolean optF;
protected boolean optV;
protected boolean optD;
protected boolean optP;
protected FileSystemView root;
protected String path;
protected InputStream in;
protected OutputStream out;
protected OutputStream err;
protected ExitCallback callback;
protected IOException error;
public ScpCommand( String[] args )
{
name = Arrays.asList( args ).toString();
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Executing command {}", name );
}
path = ".";
for ( int i = 1; i < args.length; i++ )
{
if ( args[i].charAt( 0 ) == '-' )
{
for ( int j = 1; j < args[i].length(); j++ )
{
switch ( args[i].charAt( j ) )
{
case 'f':
optF = true;
break;
case 'p':
optP = true;
break;
case 'r':
optR = true;
break;
case 't':
optT = true;
break;
case 'v':
optV = true;
break;
case 'd':
optD = true;
break;
default:
// error = new IOException("Unsupported option: " + args[i].charAt(j));
// return;
}
}
}
else if ( i == args.length - 1 )
{
path = args[args.length - 1];
}
}
if ( !optF && !optT )
{
error = new IOException( "Either -f or -t option should be set" );
}
}
public void setInputStream( InputStream in )
{
this.in = in;
}
public void setOutputStream( OutputStream out )
{
this.out = out;
}
public void setErrorStream( OutputStream err )
{
this.err = err;
}
public void setExitCallback( ExitCallback callback )
{
this.callback = callback;
}
public void start( Environment env )
throws IOException
{
if ( error != null )
{
throw error;
}
new Thread( this, "ScpCommand: " + name ).start();
}
public void destroy()
{
}
public void run()
{
int exitValue = OK;
String exitMessage = null;
try
{
if ( optT )
{
ack();
for (; ; )
{
String line;
boolean isDir = false;
int c = readAck( true );
switch ( c )
{
case -1:
return;
case 'D':
isDir = true;
case 'C':
line = ( (char) c ) + readLine();
break;
case 'E':
readLine();
return;
default:
//a real ack that has been acted upon already
continue;
}
if ( optR && isDir )
{
writeDir( line, root.getFile( path ) );
}
else
{
writeFile( line, root.getFile( path ) );
}
}
}
else if ( optF )
{
String pattern = path;
int idx = pattern.indexOf( '*' );
if ( idx >= 0 )
{
String basedir = "";
int lastSep = pattern.substring( 0, idx ).lastIndexOf( '/' );
if ( lastSep >= 0 )
{
basedir = pattern.substring( 0, lastSep );
pattern = pattern.substring( lastSep + 1 );
}
String[] included = new DirectoryScanner( basedir, pattern ).scan();
for ( String path : included )
{
SshFile file = root.getFile( basedir + "/" + path );
if ( file.isFile() )
{
readFile( file );
}
else if ( file.isDirectory() )
{
if ( !optR )
{
out.write( WARNING );
out.write( ( path + " not a regular file\n" ).getBytes() );
}
else
{
readDir( file );
}
}
else
{
out.write( WARNING );
out.write( ( path + " unknown file type\n" ).getBytes() );
}
}
}
else
{
String basedir = "";
int lastSep = pattern.lastIndexOf( '/' );
if ( lastSep >= 0 )
{
basedir = pattern.substring( 0, lastSep );
pattern = pattern.substring( lastSep + 1 );
}
SshFile file = root.getFile( basedir + "/" + pattern );
if ( !file.doesExist() )
{
exitValue = WARNING;
throw new IOException( file + ": no such file or directory" );
}
if ( file.isFile() )
{
readFile( file );
}
else if ( file.isDirectory() )
{
if ( !optR )
{
throw new IOException( file + " not a regular file" );
}
else
{
readDir( file );
}
}
else
{
throw new IOException( file + ": unknown file type" );
}
}
}
else
{
throw new IOException( "Unsupported mode" );
}
}
catch ( IOException e )
{
try
{
exitValue = ( exitValue != OK ? exitValue : ERROR );
exitMessage = e.getMessage();
out.write( exitValue );
out.write( exitMessage.getBytes() );
out.write( '\n' );
out.flush();
}
catch ( IOException e2 )
{
// Ignore
}
LOG.info( "Error in scp command", e );
}
finally
{
if ( callback != null )
{
callback.onExit( exitValue, exitMessage );
}
}
}
protected void writeDir( String header, SshFile path )
throws IOException
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Writing dir {}", path );
}
if ( !header.startsWith( "D" ) )
{
throw new IOException( "Expected a D message but got '" + header + "'" );
}
String perms = header.substring( 1, 5 );
int length = Integer.parseInt( header.substring( 6, header.indexOf( ' ', 6 ) ) );
String name = header.substring( header.indexOf( ' ', 6 ) + 1 );
if ( length != 0 )
{
throw new IOException( "Expected 0 length for directory but got " + length );
}
SshFile file;
if ( path.doesExist() && path.isDirectory() )
{
file = root.getFile( path, name );
}
else if ( !path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory() )
{
file = path;
}
else
{
throw new IOException( "Can not write to " + path );
}
if ( !( file.doesExist() && file.isDirectory() ) && !file.mkdir() )
{
throw new IOException( "Could not create directory " + file );
}
ack();
for (; ; )
{
header = readLine();
if ( header.startsWith( "C" ) )
{
writeFile( header, file );
}
else if ( header.startsWith( "D" ) )
{
writeDir( header, file );
}
else if ( header.equals( "E" ) )
{
ack();
break;
}
else
{
throw new IOException( "Unexpected message: '" + header + "'" );
}
}
}
protected void writeFile( String header, SshFile path )
throws IOException
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Writing file {}", path );
}
if ( !header.startsWith( "C" ) )
{
throw new IOException( "Expected a C message but got '" + header + "'" );
}
String perms = header.substring( 1, 5 );
long length = Long.parseLong( header.substring( 6, header.indexOf( ' ', 6 ) ) );
String name = header.substring( header.indexOf( ' ', 6 ) + 1 );
SshFile file;
if ( path.doesExist() && path.isDirectory() )
{
file = root.getFile( path, name );
}
else if ( path.doesExist() && path.isFile() )
{
file = path;
}
else if ( !path.doesExist() && path.getParentFile().doesExist() && path.getParentFile().isDirectory() )
{
file = path;
}
else
{
throw new IOException( "Can not write to " + path );
}
if ( file.doesExist() && file.isDirectory() )
{
throw new IOException( "File is a directory: " + file );
}
else if ( file.doesExist() && !file.isWritable() )
{
throw new IOException( "Can not write to file: " + file );
}
OutputStream os = file.createOutputStream( 0 );
try
{
ack();
byte[] buffer = new byte[8 * 1024];
while ( length > 0 )
{
int len = (int) Math.min( length, buffer.length );
len = in.read( buffer, 0, len );
if ( len <= 0 )
{
throw new IOException( "End of stream reached" );
}
os.write( buffer, 0, len );
length -= len;
}
}
finally
{
os.close();
}
ack();
readAck( false );
}
protected String readLine()
throws IOException
{
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (; ; )
{
int c = in.read();
if ( c == '\n' )
{
return baos.toString();
}
else if ( c == -1 )
{
throw new IOException( "End of stream" );
}
else
{
baos.write( c );
}
}
}
protected void readFile( SshFile path )
throws IOException
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Reading file {}", path );
}
StringBuilder buf = new StringBuilder();
buf.append( "C" );
buf.append( "0644" ); // what about perms
buf.append( " " );
buf.append( path.getSize() ); // length
buf.append( " " );
buf.append( path.getName() );
buf.append( "\n" );
out.write( buf.toString().getBytes() );
out.flush();
readAck( false );
InputStream is = path.createInputStream( 0 );
try
{
byte[] buffer = new byte[8 * 1024];
for (; ; )
{
int len = is.read( buffer, 0, buffer.length );
if ( len == -1 )
{
break;
}
out.write( buffer, 0, len );
}
}
finally
{
is.close();
}
ack();
readAck( false );
}
protected void readDir( SshFile path )
throws IOException
{
if ( LOG.isDebugEnabled() )
{
LOG.debug( "Reading directory {}", path );
}
StringBuilder buf = new StringBuilder();
buf.append( "D" );
buf.append( "0755" ); // what about perms
buf.append( " " );
buf.append( "0" ); // length
buf.append( " " );
buf.append( path.getName() );
buf.append( "\n" );
out.write( buf.toString().getBytes() );
out.flush();
readAck( false );
for ( SshFile child : path.listSshFiles() )
{
if ( child.isFile() )
{
readFile( child );
}
else if ( child.isDirectory() )
{
readDir( child );
}
}
out.write( "E\n".getBytes() );
out.flush();
readAck( false );
}
protected void ack()
throws IOException
{
out.write( 0 );
out.flush();
}
protected int readAck( boolean canEof )
throws IOException
{
int c = in.read();
switch ( c )
{
case -1:
if ( !canEof )
{
throw new EOFException();
}
break;
case OK:
break;
case WARNING:
LOG.warn( "Received warning: " + readLine() );
break;
case ERROR:
throw new IOException( "Received nack: " + readLine() );
default:
break;
}
return c;
}
public void setFileSystemView( FileSystemView view )
{
this.root = view;
}
}