/** * Copyright 2011-2017 Asakusa Framework Team. * * Licensed 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. */ package com.asakusafw.runtime.windows; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.text.MessageFormat; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.util.Shell; import org.xerial.snappy.OSInfo; import com.asakusafw.runtime.io.util.TemporaryFileInstaller; /** * Installs {@code winutils.exe} into the current environment. * @since 0.9.0 */ public final class WinUtilsInstaller { static final Log LOG = LogFactory.getLog(WinUtilsInstaller.class); private static final Field WINUTILS_PATH; static { Field f = null; if (Shell.WINDOWS) { try { // only Hadoop 2.x has 'Shell.WINUTILS:String' f = Shell.class.getField("WINUTILS"); //$NON-NLS-1$ f.setAccessible(true); if (f.getType() != String.class) { throw new IllegalStateException(MessageFormat.format( "incompatible Shell.WINUTILS return type: {0}", f.getType().getName())); } // Hack: we try to remove final flag from 'Shell.WINUTILS' Field modifiers = Field.class.getDeclaredField("modifiers"); //$NON-NLS-1$ modifiers.setAccessible(true); modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); } catch (Exception e) { LOG.debug("current Hadoop version does not support winutils.exe", e); //$NON-NLS-1$ } } WINUTILS_PATH = f; } private static final String ARCH_32 = "x86"; //$NON-NLS-1$ private static final String ARCH_64 = "x86_64"; //$NON-NLS-1$ private static final String SOURCE = getArchitecture() + "/winutils.exe"; //$NON-NLS-1$ private static final String TARGET_PREFIX = "winutils-" + getUserHash(); //$NON-NLS-1$ private static final String TARGET_SUFFIX = ".exe"; //$NON-NLS-1$ private static final AtomicReference<TemporaryFileInstaller> CACHE = new AtomicReference<>(); private WinUtilsInstaller() { return; } /** * Returns whether the current environment requires {@code winutils.exe} or not. * @return {@code true} if the current environment requires {@code winutils.exe}, otherwise {@code false} */ public static boolean isTarget() { return WINUTILS_PATH != null; } /** * Returns whether the current environment already has {@code winutils.exe} or not. * @return {@code true} if the current environment already has {@code winutils.exe}, otherwise {@code false} */ public static boolean isAlreadyInstalled() { if (isTarget() == false) { return false; } try { String path = (String) WINUTILS_PATH.get(null); return path != null && new File(path).canExecute(); } catch (Exception e) { LOG.debug("exception occurred while checking winutils.exe", e); //$NON-NLS-1$ return false; } } /** * Installs {@code winutils.exe} into the target directory. * @param directory the target directory * @return installed location (never null) * @throws IOException if error occurred while installing */ public static File put(File directory) throws IOException { TemporaryFileInstaller installer = prepare(); assert installer != null; // install into the default location File file = new File(directory, TARGET_PREFIX + TARGET_SUFFIX); try { LOG.info(MessageFormat.format( "installing winutils.exe into default location: {0}", //$NON-NLS-1$ file)); installer.install(file, true); return file; } catch (IOException e) { LOG.debug(MessageFormat.format( "failed to install winutils into the default location: {0}", file), e); } // install into a temporary location File temp = null; boolean success = false; try { temp = File.createTempFile(TARGET_PREFIX + '-', TARGET_SUFFIX, directory); LOG.info(MessageFormat.format( "installing winutils.exe into temporary location: {0}", temp)); installer.install(temp, false); temp.deleteOnExit(); success = true; return temp; } catch (IOException e) { LOG.debug(MessageFormat.format( "failed to install winutils into a temporary location: {0}", //$NON-NLS-1$ temp), e); } finally { if (success == false) { if (temp != null && temp.delete() == false) { LOG.warn(MessageFormat.format( "failed to delete a temporary file: {0}", //$NON-NLS-1$ temp)); } } } throw new IOException(MessageFormat.format( "error occurred while installing winutils: {0}", //$NON-NLS-1$ file)); } /** * Registers a {@code winutils.exe} file. * @param executable the executable file path, or {@code null} to remove it */ public static void register(File executable) { try { WINUTILS_PATH.set(null, executable == null ? null : executable.getAbsolutePath()); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } } private static TemporaryFileInstaller prepare() throws IOException { TemporaryFileInstaller installer = CACHE.get(); if (installer != null) { return installer; } try (InputStream input = WinUtilsInstaller.class.getResourceAsStream(SOURCE)) { if (input == null) { throw new IllegalStateException(MessageFormat.format( "missing {0} in the classpath", //$NON-NLS-1$ SOURCE)); } installer = TemporaryFileInstaller.newInstance(input, true); CACHE.compareAndSet(null, installer); } return CACHE.get(); } private static String getArchitecture() { String name = OSInfo.getArchName(); if (name.equals(ARCH_32) || name.equals(ARCH_64)) { return name; } return ARCH_64; } private static String getUserHash() { String base = System.getProperty("user.name", UUID.randomUUID().toString()); //$NON-NLS-1$ StringBuilder buf = new StringBuilder(); for (char c : base.toCharArray()) { if ('0' <= c && c <= '9' || 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || c == '_' || c == '-') { buf.append(c); } } if (buf.length() < 4) { buf.append('-'); buf.append(Integer.toHexString(base.hashCode())); } return buf.toString(); } }