|
@@ -20,27 +20,35 @@ package org.apache.hadoop.fs;
|
|
|
|
|
|
import java.io.BufferedInputStream;
|
|
|
import java.io.BufferedOutputStream;
|
|
|
+import java.io.BufferedReader;
|
|
|
import java.io.File;
|
|
|
import java.io.FileInputStream;
|
|
|
import java.io.FileNotFoundException;
|
|
|
import java.io.FileOutputStream;
|
|
|
import java.io.IOException;
|
|
|
import java.io.InputStream;
|
|
|
+import java.io.InputStreamReader;
|
|
|
import java.io.OutputStream;
|
|
|
import java.net.InetAddress;
|
|
|
import java.net.URI;
|
|
|
import java.net.UnknownHostException;
|
|
|
+import java.nio.charset.Charset;
|
|
|
import java.nio.file.AccessDeniedException;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Enumeration;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
+import java.util.concurrent.ExecutorService;
|
|
|
+import java.util.concurrent.Executors;
|
|
|
+import java.util.concurrent.Future;
|
|
|
import java.util.jar.Attributes;
|
|
|
import java.util.jar.JarOutputStream;
|
|
|
import java.util.jar.Manifest;
|
|
|
import java.util.zip.GZIPInputStream;
|
|
|
import java.util.zip.ZipEntry;
|
|
|
import java.util.zip.ZipFile;
|
|
|
+import java.util.zip.ZipInputStream;
|
|
|
|
|
|
import org.apache.commons.collections.map.CaseInsensitiveMap;
|
|
|
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
|
|
@@ -74,6 +82,11 @@ public class FileUtil {
|
|
|
* */
|
|
|
public static final int SYMLINK_NO_PRIVILEGE = 2;
|
|
|
|
|
|
+ /**
|
|
|
+ * Buffer size for copy the content of compressed file to new file.
|
|
|
+ */
|
|
|
+ private static final int BUFFER_SIZE = 8_192;
|
|
|
+
|
|
|
/**
|
|
|
* convert an array of FileStatus to an array of Path
|
|
|
*
|
|
@@ -525,6 +538,22 @@ public class FileUtil {
|
|
|
return makeShellPath(file, false);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Convert a os-native filename to a path that works for the shell
|
|
|
+ * and avoids script injection attacks.
|
|
|
+ * @param file The filename to convert
|
|
|
+ * @return The unix pathname
|
|
|
+ * @throws IOException on windows, there can be problems with the subprocess
|
|
|
+ */
|
|
|
+ public static String makeSecureShellPath(File file) throws IOException {
|
|
|
+ if (Shell.WINDOWS) {
|
|
|
+ // Currently it is never called, but it might be helpful in the future.
|
|
|
+ throw new UnsupportedOperationException("Not implemented for Windows");
|
|
|
+ } else {
|
|
|
+ return makeShellPath(file, false).replace("'", "'\\''");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Convert a os-native filename to a path that works for the shell.
|
|
|
* @param file The filename to convert
|
|
@@ -576,11 +605,48 @@ public class FileUtil {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * Given a File input it will unzip the file in a the unzip directory
|
|
|
+ * Given a stream input it will unzip the it in the unzip directory.
|
|
|
+ * passed as the second parameter
|
|
|
+ * @param inputStream The zip file as input
|
|
|
+ * @param toDir The unzip directory where to unzip the zip file.
|
|
|
+ * @throws IOException an exception occurred
|
|
|
+ */
|
|
|
+ public static void unZip(InputStream inputStream, File toDir)
|
|
|
+ throws IOException {
|
|
|
+ try (ZipInputStream zip = new ZipInputStream(inputStream)) {
|
|
|
+ int numOfFailedLastModifiedSet = 0;
|
|
|
+ for(ZipEntry entry = zip.getNextEntry();
|
|
|
+ entry != null;
|
|
|
+ entry = zip.getNextEntry()) {
|
|
|
+ if (!entry.isDirectory()) {
|
|
|
+ File file = new File(toDir, entry.getName());
|
|
|
+ File parent = file.getParentFile();
|
|
|
+ if (!parent.mkdirs() &&
|
|
|
+ !parent.isDirectory()) {
|
|
|
+ throw new IOException("Mkdirs failed to create " +
|
|
|
+ parent.getAbsolutePath());
|
|
|
+ }
|
|
|
+ try (OutputStream out = new FileOutputStream(file)) {
|
|
|
+ IOUtils.copyBytes(zip, out, BUFFER_SIZE);
|
|
|
+ }
|
|
|
+ if (!file.setLastModified(entry.getTime())) {
|
|
|
+ numOfFailedLastModifiedSet++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (numOfFailedLastModifiedSet > 0) {
|
|
|
+ LOG.warn("Could not set last modfied time for {} file(s)",
|
|
|
+ numOfFailedLastModifiedSet);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Given a File input it will unzip it in the unzip directory.
|
|
|
* passed as the second parameter
|
|
|
* @param inFile The zip file as input
|
|
|
* @param unzipDir The unzip directory where to unzip the zip file.
|
|
|
- * @throws IOException
|
|
|
+ * @throws IOException An I/O exception has occurred
|
|
|
*/
|
|
|
public static void unZip(File inFile, File unzipDir) throws IOException {
|
|
|
Enumeration<? extends ZipEntry> entries;
|
|
@@ -620,6 +686,138 @@ public class FileUtil {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Run a command and send the contents of an input stream to it.
|
|
|
+ * @param inputStream Input stream to forward to the shell command
|
|
|
+ * @param command shell command to run
|
|
|
+ * @throws IOException read or write failed
|
|
|
+ * @throws InterruptedException command interrupted
|
|
|
+ * @throws ExecutionException task submit failed
|
|
|
+ */
|
|
|
+ private static void runCommandOnStream(
|
|
|
+ InputStream inputStream, String command)
|
|
|
+ throws IOException, InterruptedException, ExecutionException {
|
|
|
+ ExecutorService executor = null;
|
|
|
+ ProcessBuilder builder = new ProcessBuilder();
|
|
|
+ builder.command(
|
|
|
+ Shell.WINDOWS ? "cmd" : "bash",
|
|
|
+ Shell.WINDOWS ? "/c" : "-c",
|
|
|
+ command);
|
|
|
+ Process process = builder.start();
|
|
|
+ int exitCode;
|
|
|
+ try {
|
|
|
+ // Consume stdout and stderr, to avoid blocking the command
|
|
|
+ executor = Executors.newFixedThreadPool(2);
|
|
|
+ Future output = executor.submit(() -> {
|
|
|
+ try {
|
|
|
+ // Read until the output stream receives an EOF and closed.
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ // Log directly to avoid out of memory errors
|
|
|
+ try (BufferedReader reader =
|
|
|
+ new BufferedReader(
|
|
|
+ new InputStreamReader(process.getInputStream(),
|
|
|
+ Charset.forName("UTF-8")))) {
|
|
|
+ String line;
|
|
|
+ while((line = reader.readLine()) != null) {
|
|
|
+ LOG.debug(line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ org.apache.commons.io.IOUtils.copy(
|
|
|
+ process.getInputStream(),
|
|
|
+ new IOUtils.NullOutputStream());
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.debug(e.getMessage());
|
|
|
+ }
|
|
|
+ });
|
|
|
+ Future error = executor.submit(() -> {
|
|
|
+ try {
|
|
|
+ // Read until the error stream receives an EOF and closed.
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
+ // Log directly to avoid out of memory errors
|
|
|
+ try (BufferedReader reader =
|
|
|
+ new BufferedReader(
|
|
|
+ new InputStreamReader(process.getErrorStream(),
|
|
|
+ Charset.forName("UTF-8")))) {
|
|
|
+ String line;
|
|
|
+ while((line = reader.readLine()) != null) {
|
|
|
+ LOG.debug(line);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ org.apache.commons.io.IOUtils.copy(
|
|
|
+ process.getErrorStream(),
|
|
|
+ new IOUtils.NullOutputStream());
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ LOG.debug(e.getMessage());
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Pass the input stream to the command to process
|
|
|
+ try {
|
|
|
+ org.apache.commons.io.IOUtils.copy(
|
|
|
+ inputStream, process.getOutputStream());
|
|
|
+ } finally {
|
|
|
+ process.getOutputStream().close();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Wait for both stdout and stderr futures to finish
|
|
|
+ error.get();
|
|
|
+ output.get();
|
|
|
+ } finally {
|
|
|
+ // Clean up the threads
|
|
|
+ if (executor != null) {
|
|
|
+ executor.shutdown();
|
|
|
+ }
|
|
|
+ // Wait to avoid leaking the child process
|
|
|
+ exitCode = process.waitFor();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (exitCode != 0) {
|
|
|
+ throw new IOException(
|
|
|
+ String.format(
|
|
|
+ "Error executing command. %s " +
|
|
|
+ "Process exited with exit code %d.",
|
|
|
+ command, exitCode));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Given a Tar File as input it will untar the file in a the untar directory
|
|
|
+ * passed as the second parameter
|
|
|
+ *
|
|
|
+ * This utility will untar ".tar" files and ".tar.gz","tgz" files.
|
|
|
+ *
|
|
|
+ * @param inputStream The tar file as input.
|
|
|
+ * @param untarDir The untar directory where to untar the tar file.
|
|
|
+ * @param gzipped The input stream is gzipped
|
|
|
+ * TODO Use magic number and PusbackInputStream to identify
|
|
|
+ * @throws IOException an exception occurred
|
|
|
+ * @throws InterruptedException command interrupted
|
|
|
+ * @throws ExecutionException task submit failed
|
|
|
+ */
|
|
|
+ public static void unTar(InputStream inputStream, File untarDir,
|
|
|
+ boolean gzipped)
|
|
|
+ throws IOException, InterruptedException, ExecutionException {
|
|
|
+ if (!untarDir.mkdirs()) {
|
|
|
+ if (!untarDir.isDirectory()) {
|
|
|
+ throw new IOException("Mkdirs failed to create " + untarDir);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(Shell.WINDOWS) {
|
|
|
+ // Tar is not native to Windows. Use simple Java based implementation for
|
|
|
+ // tests and simple tar archives
|
|
|
+ unTarUsingJava(inputStream, untarDir, gzipped);
|
|
|
+ } else {
|
|
|
+ // spawn tar utility to untar archive for full fledged unix behavior such
|
|
|
+ // as resolving symlinks in tar archives
|
|
|
+ unTarUsingTar(inputStream, untarDir, gzipped);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Given a Tar File as input it will untar the file in a the untar directory
|
|
|
* passed as the second parameter
|
|
@@ -650,23 +848,41 @@ public class FileUtil {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static void unTarUsingTar(InputStream inputStream, File untarDir,
|
|
|
+ boolean gzipped)
|
|
|
+ throws IOException, InterruptedException, ExecutionException {
|
|
|
+ StringBuilder untarCommand = new StringBuilder();
|
|
|
+ if (gzipped) {
|
|
|
+ untarCommand.append("gzip -dc | (");
|
|
|
+ }
|
|
|
+ untarCommand.append("cd '");
|
|
|
+ untarCommand.append(FileUtil.makeSecureShellPath(untarDir));
|
|
|
+ untarCommand.append("' && ");
|
|
|
+ untarCommand.append("tar -x ");
|
|
|
+
|
|
|
+ if (gzipped) {
|
|
|
+ untarCommand.append(")");
|
|
|
+ }
|
|
|
+ runCommandOnStream(inputStream, untarCommand.toString());
|
|
|
+ }
|
|
|
+
|
|
|
private static void unTarUsingTar(File inFile, File untarDir,
|
|
|
boolean gzipped) throws IOException {
|
|
|
StringBuffer untarCommand = new StringBuffer();
|
|
|
if (gzipped) {
|
|
|
untarCommand.append(" gzip -dc '");
|
|
|
- untarCommand.append(FileUtil.makeShellPath(inFile));
|
|
|
+ untarCommand.append(FileUtil.makeSecureShellPath(inFile));
|
|
|
untarCommand.append("' | (");
|
|
|
}
|
|
|
untarCommand.append("cd '");
|
|
|
- untarCommand.append(FileUtil.makeShellPath(untarDir));
|
|
|
- untarCommand.append("' ; ");
|
|
|
+ untarCommand.append(FileUtil.makeSecureShellPath(untarDir));
|
|
|
+ untarCommand.append("' && ");
|
|
|
untarCommand.append("tar -xf ");
|
|
|
|
|
|
if (gzipped) {
|
|
|
untarCommand.append(" -)");
|
|
|
} else {
|
|
|
- untarCommand.append(FileUtil.makeShellPath(inFile));
|
|
|
+ untarCommand.append(FileUtil.makeSecureShellPath(inFile));
|
|
|
}
|
|
|
String[] shellCmd = { "bash", "-c", untarCommand.toString() };
|
|
|
ShellCommandExecutor shexec = new ShellCommandExecutor(shellCmd);
|
|
@@ -701,6 +917,29 @@ public class FileUtil {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private static void unTarUsingJava(InputStream inputStream, File untarDir,
|
|
|
+ boolean gzipped) throws IOException {
|
|
|
+ TarArchiveInputStream tis = null;
|
|
|
+ try {
|
|
|
+ if (gzipped) {
|
|
|
+ inputStream = new BufferedInputStream(new GZIPInputStream(
|
|
|
+ inputStream));
|
|
|
+ } else {
|
|
|
+ inputStream =
|
|
|
+ new BufferedInputStream(inputStream);
|
|
|
+ }
|
|
|
+
|
|
|
+ tis = new TarArchiveInputStream(inputStream);
|
|
|
+
|
|
|
+ for (TarArchiveEntry entry = tis.getNextTarEntry(); entry != null;) {
|
|
|
+ unpackEntries(tis, entry, untarDir);
|
|
|
+ entry = tis.getNextTarEntry();
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ IOUtils.cleanupWithLogger(LOG, tis, inputStream);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private static void unpackEntries(TarArchiveInputStream tis,
|
|
|
TarArchiveEntry entry, File outputDir) throws IOException {
|
|
|
if (entry.isDirectory()) {
|