com.xebialabs.overthere.ssh.SshScpFile Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of overthere Show documentation
Show all versions of overthere Show documentation
Remote file manipulation and process execution framework for Java
/**
* Copyright (c) 2008-2016, XebiaLabs B.V., All rights reserved.
*
*
* Overthere is licensed under the terms of the GPLv2
* , like most XebiaLabs Libraries.
* There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
* this software, see the FLOSS License Exception
* .
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; version 2
* of the License.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
package com.xebialabs.overthere.ssh;
import com.xebialabs.overthere.CmdLine;
import com.xebialabs.overthere.OverthereFile;
import com.xebialabs.overthere.RuntimeIOException;
import com.xebialabs.overthere.util.CapturingOverthereExecutionOutputHandler;
import net.schmizz.sshj.xfer.scp.SCPUploadClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.xebialabs.overthere.CmdLine.build;
import static com.xebialabs.overthere.ssh.SshConnection.NOCD_PSEUDO_COMMAND;
import static com.xebialabs.overthere.util.CapturingOverthereExecutionOutputHandler.capturingHandler;
import static com.xebialabs.overthere.util.LoggingOverthereExecutionOutputHandler.loggingErrorHandler;
import static com.xebialabs.overthere.util.LoggingOverthereExecutionOutputHandler.loggingOutputHandler;
import static com.xebialabs.overthere.util.MultipleOverthereExecutionOutputHandler.multiHandler;
import static com.xebialabs.overthere.util.NullOverthereExecutionOutputHandler.swallow;
import static java.lang.String.format;
/**
* A file on a host connected through SSH w/ SCP.
*/
class SshScpFile extends SshFile {
private static final String PERMISSIONS_TOKEN_PATTERN = ".*?([dl\\-]([r\\-][w\\-][xsStT\\-]){3}[@\\.\\+]*)";
private static Pattern permissionsTokenPattern = Pattern.compile(PERMISSIONS_TOKEN_PATTERN);
/**
* Constructs an SshScpOverthereFile
*
* @param connection the connection to the host
* @param remotePath the path of the file on the host
*/
public SshScpFile(SshScpConnection connection, String remotePath) {
super(connection, remotePath);
}
@Override
public boolean exists() {
return getFileInfo().exists;
}
@Override
public boolean canRead() {
return getFileInfo().canRead;
}
@Override
public boolean canWrite() {
return getFileInfo().canWrite;
}
@Override
public boolean canExecute() {
return getFileInfo().canExecute;
}
@Override
public boolean isFile() {
return getFileInfo().isFile;
}
@Override
public boolean isDirectory() {
return getFileInfo().isDirectory;
}
@Override
public long lastModified() {
// FIXME: Implement by parsing the date output of `ls -l`
throw new UnsupportedOperationException();
}
@Override
public long length() {
return getFileInfo().length;
}
/**
* Gets information about the file by executing "ls -ld" on it.
*
* @return the information about the file, never null
.
* @throws RuntimeIOException if an I/O exception occurs
*/
public LsResults getFileInfo() throws RuntimeIOException {
logger.debug("Retrieving file info of {}", this);
CmdLine lsCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.getFileInfoCommand, getPath());
LsResults results = new LsResults();
CapturingOverthereExecutionOutputHandler capturedOutput = capturingHandler();
int errno = executeCommand(capturedOutput, swallow(), lsCmdLine);
if (errno == 0) {
for (int i = capturedOutput.getOutputLines().size() - 1; i >= 0; i--) {
if (parseLsOutputLine(results, capturedOutput.getOutputLines().get(i))) {
results.exists = true;
break;
}
}
if (!results.exists) {
throw new RuntimeIOException("ls -ld " + getPath() + " returned " + errno + " but its output is unparseable: " + capturedOutput.getOutput());
}
} else {
results.exists = false;
}
logger.debug("Listed file {}: exists={}, isDirectory={}, length={}, canRead={}, canWrite={}, canExecute={}", this, results.exists,
results.isDirectory, results.length
, results.canRead, results.canWrite, results.canExecute);
return results;
}
protected boolean parseLsOutputLine(LsResults results, String outputLine) {
StringTokenizer outputTokens = new StringTokenizer(outputLine);
if (outputTokens.countTokens() < 5) {
logger.debug("Not parsing ls output line [{}] because it has less than 5 tokens", outputLine);
return false;
}
String permissions = outputTokens.nextToken();
Matcher matcher = permissionsTokenPattern.matcher(permissions);
if (!matcher.matches()) {
logger.debug("Not parsing ls output line [{}] because it the first token does not match the pattern for permissions [" + PERMISSIONS_TOKEN_PATTERN
+ "]", outputLine);
return false;
} else {
permissions = matcher.group(1);
}
logger.debug("Parsing ls output line [{}]", outputLine);
outputTokens.nextToken(); // inodelinks
outputTokens.nextToken(); // owner
outputTokens.nextToken(); // group
String size = outputTokens.nextToken();
results.isFile = permissions.length() >= 1 && permissions.charAt(0) == '-';
results.isDirectory = permissions.length() >= 1 && permissions.charAt(0) == 'd';
results.canRead = permissions.length() >= 2 && permissions.charAt(1) == 'r';
results.canWrite = permissions.length() >= 3 && permissions.charAt(2) == 'w';
results.canExecute = permissions.length() >= 4 && (permissions.charAt(3) == 'x' || permissions.charAt(3) == 's' || permissions.charAt(3) == 't');
try {
results.length = Integer.parseInt(size);
} catch (NumberFormatException exc) {
logger.warn("Cannot parse length of " + this.getPath() + " from ls output: " + outputLine + ". Length will be reported as -1.", exc);
}
return true;
}
/**
* Holds results of an ls call
*/
public static class LsResults {
public boolean exists;
public boolean isFile;
public boolean isDirectory;
public long length = -1;
public boolean canRead;
public boolean canWrite;
public boolean canExecute;
}
@Override
public InputStream getInputStream() throws RuntimeIOException {
try {
final File tempFile = File.createTempFile("scp_download", ".tmp");
tempFile.deleteOnExit();
logger.debug("Downloading contents of {} to temporary file {}", this, tempFile);
connection.getSshClient().newSCPFileTransfer().download(getPath(), tempFile.getPath());
logger.debug("Opening input stream to temporary file {} to retrieve contents downloaded from {}. Temporary file will be deleted when the stream is closed", tempFile, this);
return asBuffered(new FileInputStream(tempFile) {
@Override
public void close() throws IOException {
logger.debug("Closing input stream to temporary file {}", tempFile);
try {
super.close();
} finally {
logger.debug("Deleting temporary file {}", tempFile);
tempFile.delete();
}
}
});
} catch (IOException exc) {
throw new RuntimeIOException(format("Cannot open %s for reading: %s", this, exc.toString()), exc);
}
}
@Override
public OutputStream getOutputStream() throws RuntimeIOException {
try {
final File tempFile = File.createTempFile("scp_upload", ".tmp");
tempFile.deleteOnExit();
logger.debug("Opening output stream to temporary file {} to store contents to be uploaded to {} when the stream is closed", tempFile, SshScpFile.this);
return asBuffered(new FileOutputStream(tempFile) {
@Override
public void close() throws IOException {
logger.debug("Closing output stream to temporary file {}", tempFile);
try {
super.close();
} finally {
uploadAndDelete(tempFile);
}
}
private void uploadAndDelete(File tempFile) throws IOException {
logger.debug("Uploading contents of temporary file {} to to {}", tempFile, SshScpFile.this);
try {
connection.getSshClient().newSCPFileTransfer().upload(tempFile.getPath(), getPath());
} finally {
logger.debug("Deleting temporary file {}", tempFile);
tempFile.delete();
}
}
});
} catch (IOException exc) {
throw new RuntimeIOException(format("Cannot open %s for writing: %s", this, exc.toString()), exc);
}
}
@Override
public List listFiles() {
logger.debug("Listing directory {}", this);
CmdLine lsCmdLine = build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.listFilesCommand, getPath());
CapturingOverthereExecutionOutputHandler capturedStdout = capturingHandler();
CapturingOverthereExecutionOutputHandler capturedStderr = capturingHandler();
int errno = executeCommand(multiHandler(loggingOutputHandler(logger), capturedStdout), multiHandler(loggingErrorHandler(logger), capturedStderr), lsCmdLine);
if (errno != 0) {
throw new RuntimeIOException("Cannot list directory " + this + ": " + capturedStderr.getOutput() + " (errno=" + errno + ")");
}
List files = new ArrayList<>();
for (String lsLine : capturedStdout.getOutputLines()) {
// Filter out the '.' and '..'
if (!(".".equals(lsLine) || "..".equals(lsLine))) {
files.add(connection.getFile(this, lsLine));
}
}
return files;
}
@Override
public void mkdir() {
logger.debug("Creating directory {}", this);
mkdir(connection.mkdirCommand);
}
@Override
public void mkdirs() {
logger.debug("Creating directories {}", this);
mkdir(connection.mkdirsCommand);
}
protected void mkdir(String command) throws RuntimeIOException {
CmdLine mkdirCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(command, getPath());
executeAndThrowOnErrorCode(mkdirCmdLine, "Cannot create directory or -ies " + this);
if (logger.isDebugEnabled()) {
logger.debug("Created directory " + this + " (using command: " + command + ")");
}
}
@Override
public void renameTo(OverthereFile dest) {
logger.debug("Renaming {} to {}", this, dest);
if (dest instanceof SshScpFile) {
SshScpFile sshScpDestFile = (SshScpFile) dest;
if (sshScpDestFile.getConnection() == getConnection()) {
CmdLine mvCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.renameToCommand, getPath(), sshScpDestFile.getPath());
executeAndThrowOnErrorCode(mvCmdLine, "Cannot rename file/directory " + this);
} else {
throw new RuntimeIOException("Cannot rename " + connection.protocolAndConnectionType + " file/directory " + this
+ " to file/directory " + dest + " because it is in a different connection");
}
} else {
throw new RuntimeIOException("Cannot rename " + connection.protocolAndConnectionType + " file/directory " + this
+ " to non-" + connection.protocolAndConnectionType + " file/directory " + dest);
}
}
@Override
public void setExecutable(boolean executable) {
logger.debug("Setting execute permission on {} to {}", this, executable);
CmdLine chmodCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(executable ? connection.setExecutableCommand : connection.setNotExecutableCommand, getPath());
executeAndThrowOnErrorCode(chmodCmdLine, "Cannot set execute permission on file " + this + " to " + executable);
}
@Override
protected void deleteDirectory() {
logger.debug("Deleting directory {}", this);
CmdLine rmdirCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.deleteDirectoryCommand, getPath());
executeAndThrowOnErrorCode(rmdirCmdLine, "Cannot delete directory " + this);
}
@Override
protected void deleteFile() {
logger.debug("Deleting file {}", this);
CmdLine rmCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.deleteFileCommand, getPath());
executeAndThrowOnErrorCode(rmCmdLine, "Cannot delete file " + this);
}
@Override
public void deleteRecursively() throws RuntimeIOException {
logger.debug("Recursively deleting file or directory {}", this);
CmdLine rmCmdLine = CmdLine.build(NOCD_PSEUDO_COMMAND).addTemplatedFragment(connection.deleteRecursivelyCommand, getPath());
executeAndThrowOnErrorCode(rmCmdLine, "Cannot recursively delete file or directory " + this);
}
@Override
protected void copyFrom(OverthereFile source) {
logger.debug("Copying file or directory {} to {}", source, this);
SCPUploadClient uploadClient = connection.getSshClient().newSCPFileTransfer().newSCPUploadClient();
try {
if (source.isDirectory() && this.exists()) {
for (OverthereFile sourceFile : source.listFiles()) {
uploadClient.copy(new OverthereFileLocalSourceFile(sourceFile), getPath());
}
} else {
uploadClient.copy(new OverthereFileLocalSourceFile(source), getPath());
}
} catch (IOException e) {
throw new RuntimeIOException("Cannot copy " + source + " to " + this + ": " + e.toString(), e);
}
}
private void executeAndThrowOnErrorCode(CmdLine mkdirCmdLine, String message) {
CapturingOverthereExecutionOutputHandler capturedStderr = capturingHandler();
int errno = executeCommand(loggingOutputHandler(logger), multiHandler(loggingErrorHandler(logger), capturedStderr), mkdirCmdLine);
if (errno != 0) {
throw new RuntimeIOException(format("%s: %s (errno=%d)", message, capturedStderr.getOutput(), errno));
}
}
private static Logger logger = LoggerFactory.getLogger(SshScpFile.class);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy