/* * 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, either version 3 of the License, or * (at your option) any later version. * * 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, see <http://www.gnu.org/licenses/>. */ /* * Binaries.java * Copyright (C) 2017-2018 University of Waikato, Hamilton, New Zealand */ package com.github.fracpete.rsync4j.core; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.SystemUtils; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Helper class for dealing with the rsync binaries. * On Linux and OSX, rsync and ssh must be present. * * @author FracPete (fracpete at waikato dot ac dot nz) */ public class Binaries { /** the resource prefix. */ public final static String RESOURCE_DIR = "com/github/fracpete/rsync4j/"; /** the sub-directory for the windows 32bit binaries. */ public final static String WINDOWS_DIR_32 = "windows-x86/"; /** the sub-directory for the windows 64bit binaries. */ public final static String WINDOWS_DIR_64 = "windows-x86_64/"; /** the windows environment variable for the rsyncj4 home directory. */ public final static String WINDOWS_HOME_DIR = "RSYNC4J_HOME"; public static final String LIBRARIES = "libraries.txt"; /** for storing any previously extract binary. */ protected static Boolean binariesExtracted; /** the rsync binary. */ protected static String rsyncBinary; /** the ssh binary. */ protected static String sshBinary; /** the ssh-keygen binary. */ protected static String sshkeygenBinary; /** for logging. */ protected static Logger LOGGER = Logger.getLogger(Binaries.class.getName()); /** * Returns the "bitness", ie 32 or 64 bit of the underlying OS. * * @return the number of bits */ public synchronized static int getBitness() { String arch; arch = System.getProperty("os.arch"); if (arch.endsWith("86")) return 32; else if (arch.endsWith("64")) return 64; else throw new IllegalStateException("Cannot interpret 'os.arch' for bitness: " + arch); } /** * Copies the specified resource to the output directory. * * @param inDir the resource directory to use * @param name the name of the resource * @param outDir the output directory * @return the full path */ protected static String copyResourceTo(String inDir, String name, String outDir) throws Exception { String result; String resource; InputStream is; BufferedInputStream bis; String outFull; File out; FileOutputStream fos; BufferedOutputStream bos; result = null; is = null; bis = null; fos = null; bos = null; try { resource = inDir; if (!resource.endsWith("/")) resource += "/"; resource += name; outFull = outDir + File.separator + name; LOGGER.info("Copying resource '" + resource + "' to '" + outFull + "'"); is = Binaries.class.getClassLoader().getResourceAsStream(resource); bis = new BufferedInputStream(is); out = new File(outFull); fos = new FileOutputStream(out); bos = new BufferedOutputStream(fos); IOUtils.copy(bis, bos); result = out.getAbsolutePath(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Copying failed!", e); throw e; } finally { IOUtils.closeQuietly(bis); IOUtils.closeQuietly(is); IOUtils.closeQuietly(bos); IOUtils.closeQuietly(fos); } return result; } /** * Returns the home directory to use for rsync4j. * On Windows, use the {@link #WINDOWS_HOME_DIR} environment variable * to point it to a custom location. * * @return the directory */ protected static String homeDir() { String result; File dir; result = System.getProperty("user.home") + File.separator + "rsync4j"; if (SystemUtils.IS_OS_WINDOWS) { if (System.getenv(WINDOWS_HOME_DIR) != null) { dir = new File(System.getenv(WINDOWS_HOME_DIR)); if (!dir.exists() || dir.isDirectory()) result = dir.getAbsolutePath(); } } return result; } /** * Returns the sub-directory for the Windows binaries, depending on the * bitness. * * @return the sub-directory of the binaries */ protected static String getWindowsDir() { if (getBitness() == 32) return WINDOWS_DIR_32; else return WINDOWS_DIR_64; } /** * Returns the list of libraries to copy. * * @return the libraries * @throws Exception if reading */ protected static List<String> getLibraries() throws Exception { List<String> result; String resource; InputStream is; StringBuilder buf; int c; result = new ArrayList<>(); resource = RESOURCE_DIR + getWindowsDir() + LIBRARIES; is = null; try { buf = new StringBuilder(); is = Binaries.class.getClassLoader().getResourceAsStream(resource); while ((c = is.read()) != -1) buf.append((char) c); result.addAll(Arrays.asList(buf.toString().split("\n"))); } catch (Exception e) { throw new IllegalStateException("Failed to read list of libraries from: " + resource); } finally { IOUtils.closeQuietly(is); } return result; } /** * Extracts the binaries to the tmp directory. * * @throws Exception if extraction fails */ protected static void extractBinaries() throws Exception { String homeDir; String winDir; String binDir; String sshDir; File dir; sshBinary = "/usr/bin/ssh"; rsyncBinary = "/usr/bin/rsync"; sshkeygenBinary = "/usr/bin/ssh-keygen"; if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC_OSX) { if (!new File(rsyncBinary).exists()) throw new IllegalStateException("rsync not installed (" + rsyncBinary + ")?"); if (!new File(sshBinary).exists()) throw new IllegalStateException("ssh not installed (" + sshBinary + ")?"); if (!new File(sshkeygenBinary).exists()) throw new IllegalStateException("ssh-keygen not installed (" + sshkeygenBinary + ")?"); } else if (SystemUtils.IS_OS_WINDOWS) { homeDir = homeDir(); binDir = homeDir + File.separator + "bin"; sshDir = homeDir + File.separator + "home" + File.separator + System.getProperty("user.name") + File.separator + ".ssh"; if (!new File(binDir + File.separator + "rsync.exe").exists()) { LOGGER.info("Setting up rsync4j environment in '" + homeDir + "'..."); dir = new File(binDir); if (!dir.exists()) { LOGGER.info("Creating directory: " + dir); if (!dir.mkdirs()) throw new IllegalStateException("Failed to create directory: " + dir); } dir = new File(sshDir); if (!dir.exists()) { LOGGER.info("Creating directory: " + dir); if (!dir.mkdirs()) throw new IllegalStateException("Failed to create directory: " + dir); } LOGGER.info("Copy your public key pairs into: " + dir); winDir = getWindowsDir(); for (String lib: getLibraries()) copyResourceTo(RESOURCE_DIR + winDir, lib, binDir); sshBinary = copyResourceTo(RESOURCE_DIR + winDir, "ssh.exe", binDir); rsyncBinary = copyResourceTo(RESOURCE_DIR + winDir, "rsync.exe", binDir); sshkeygenBinary = copyResourceTo(RESOURCE_DIR + winDir, "ssh-keygen.exe", binDir); } else { sshBinary = binDir + File.separator + "ssh.exe"; rsyncBinary = binDir + File.separator + "rsync.exe"; sshkeygenBinary = binDir + File.separator + "ssh-keygen.exe"; } } else { throw new IllegalStateException( "Unsupported operating system: " + SystemUtils.OS_NAME + "/" + SystemUtils.OS_ARCH + "/" + SystemUtils.OS_VERSION); } binariesExtracted = true; } /** * Returns the rsync binary (if necessary extracts the binaries to the temp directory). * * @return the filename of the binary * @throws Exception if extraction fails */ public static String rsyncBinary() throws Exception { if (binariesExtracted == null) extractBinaries(); return rsyncBinary; } /** * Returns the ssh binary (if necessary extracts the binaries to the temp directory). * * @return the filename of the binary * @throws Exception if extraction fails */ public static String sshBinary() throws Exception { if (binariesExtracted == null) extractBinaries(); return sshBinary; } /** * Returns the ssh-keygen binary (if necessary extracts the binaries to the temp directory). * * @return the filename of the binary * @throws Exception if extraction fails */ public static String sshkeygenBinary() throws Exception { if (binariesExtracted == null) extractBinaries(); return sshkeygenBinary; } /** * Converts the path into cygwin notation on Windows platform. * * @param path the path to convert * @return the (potentially) converted path */ public static String convertPath(String path) { String result; result = path; if (SystemUtils.IS_OS_WINDOWS) { result = result.replace("\\", "/"); if (result.matches("^[a-zA-Z]:.*")) result = "/cygdrive/" + result.substring(0, 1).toLowerCase() + "/" + result.substring(2); } return result; } }