blob: 5f233b50126ed45ed8bfe67569a371c9bd06d44b [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.android.tradefed.util;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import org.apache.commons.compress.archivers.ArchiveException;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.utils.IOUtils;
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.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Utility to manipulate a tar file. It wraps the commons-compress in order to provide tar support.
*/
public class TarUtil {
private static final byte[] GZIP_SIGNATURE = {0x1f, (byte) 0x8b};
/**
* Determine whether a file is a gzip.
*
* @param file the file to check.
* @return whether the file is a gzip.
* @throws IOException if the file could not be read.
*/
public static boolean isGzip(File file) throws IOException {
byte[] signature = new byte[GZIP_SIGNATURE.length];
try (InputStream stream = new FileInputStream(file)) {
if (stream.read(signature) != signature.length) {
return false;
}
}
return Arrays.equals(GZIP_SIGNATURE, signature);
}
/**
* Untar a tar file into a directory. tar.gz file needs to be {@link #unGzip(File, File)} first.
*
* @param inputFile The tar file to extract
* @param outputDir the directory where to put the extracted files.
* @return The list of {@link File} untarred.
* @throws FileNotFoundException
* @throws IOException
*/
public static List<File> unTar(final File inputFile, final File outputDir)
throws FileNotFoundException, IOException {
return unTar(inputFile, outputDir, entryName -> true);
}
/**
* Untar a tar file into a directory. tar.gz file needs to be {@link #unGzip(File, File)} first.
*
* @param inputFile The tar file to extract
* @param outputDir the directory where to put the extracted files.
* @param fileNames the files to be extracted from the tar.
* @return The list of {@link File} untarred.
* @throws FileNotFoundException
* @throws IOException
*/
public static List<File> unTar(
final File inputFile, final File outputDir, Collection<String> fileNames)
throws FileNotFoundException, IOException {
HashSet<String> fileNameSet = new HashSet<>(fileNames);
List<File> untarredFiles =
unTar(inputFile, outputDir, (entryName) -> fileNameSet.contains(entryName));
if (untarredFiles.size() != fileNameSet.size()) {
HashSet<File> untarredFileSet = new HashSet<File>(untarredFiles);
String missingFileNames =
fileNameSet.stream()
.filter(name -> !untarredFileSet.contains(new File(outputDir, name)))
.collect(Collectors.joining(" "));
throw new IOException(
String.format("Couldn't find files in %s: %s", inputFile, missingFileNames));
}
return untarredFiles;
}
/**
* Untar a tar file into a directory. tar.gz file needs to be {@link #unGzip(File, File)} first.
*
* @param inputFile The tar file to extract
* @param outputDir the directory where to put the extracted files.
* @param shouldExtract a function that takes an entry name as input and returns whether the
* entry should be extracted.
* @return The list of {@link File} untarred.
* @throws FileNotFoundException
* @throws IOException
*/
private static List<File> unTar(
final File inputFile, final File outputDir, Predicate<String> shouldExtract)
throws FileNotFoundException, IOException {
CLog.i(String.format("Untaring %s to dir %s.", inputFile.getAbsolutePath(),
outputDir.getAbsolutePath()));
final List<File> untaredFiles = new LinkedList<File>();
final InputStream is = new FileInputStream(inputFile);
TarArchiveInputStream debInputStream = null;
try {
debInputStream = (TarArchiveInputStream)
new ArchiveStreamFactory().createArchiveInputStream("tar", is);
TarArchiveEntry entry = null;
while ((entry = (TarArchiveEntry)debInputStream.getNextEntry()) != null) {
if (!shouldExtract.test(entry.getName())) {
continue;
}
final File outputFile = new File(outputDir, entry.getName());
if (entry.isDirectory()) {
CLog.i(String.format("Attempting to write output directory %s.",
outputFile.getAbsolutePath()));
if (!outputFile.exists()) {
CLog.i(String.format("Attempting to create output directory %s.",
outputFile.getAbsolutePath()));
if (!outputFile.mkdirs()) {
throw new IllegalStateException(
String.format("Couldn't create directory %s.",
outputFile.getAbsolutePath()));
}
}
} else {
CLog.i(String.format("Creating output file %s.", outputFile.getAbsolutePath()));
final File parent = outputFile.getParentFile();
if (parent != null && !parent.exists()) {
if (!parent.mkdirs()) {
throw new IOException(
String.format(
"Couldn't create directory %s.",
parent.getAbsolutePath()));
}
}
final OutputStream outputFileStream = new FileOutputStream(outputFile);
IOUtils.copy(debInputStream, outputFileStream);
StreamUtil.close(outputFileStream);
}
untaredFiles.add(outputFile);
}
} catch (ArchiveException ae) {
// We rethrow the ArchiveException through a more generic one.
throw new IOException(ae);
} finally {
StreamUtil.close(debInputStream);
StreamUtil.close(is);
}
return untaredFiles;
}
/**
* UnGZip a file: a tar.gz file will become a tar file.
*
* @param inputFile The {@link File} to ungzip
* @param outputDir The directory where to put the ungzipped file.
* @return a {@link File} pointing to the ungzipped file.
* @throws FileNotFoundException
* @throws IOException
*/
public static File unGzip(final File inputFile, final File outputDir)
throws FileNotFoundException, IOException {
CLog.i(String.format("Ungzipping %s to dir %s.", inputFile.getAbsolutePath(),
outputDir.getAbsolutePath()));
// rename '-3' to remove the '.gz' extension.
final File outputFile = new File(outputDir, inputFile.getName().substring(0,
inputFile.getName().length() - 3));
GZIPInputStream in = null;
FileOutputStream out = null;
try {
in = new GZIPInputStream(new FileInputStream(inputFile));
out = new FileOutputStream(outputFile);
IOUtils.copy(in, out);
} finally {
StreamUtil.close(in);
StreamUtil.close(out);
}
return outputFile;
}
/**
* Utility function to gzip (.gz) a file. the .gz extension will be added to base file name.
*
* @param inputFile the {@link File} to be gzipped.
* @return the gzipped file.
* @throws IOException
*/
public static File gzip(final File inputFile) throws IOException {
File outputFile = FileUtil.createTempFile(inputFile.getName(), ".gz");
GZIPOutputStream out = null;
FileInputStream in = null;
try {
out = new GZIPOutputStream(new FileOutputStream(outputFile));
in = new FileInputStream(inputFile);
IOUtils.copy(in, out);
} catch (IOException e) {
// delete the tmp file if we failed to gzip.
FileUtil.deleteFile(outputFile);
throw e;
} finally {
StreamUtil.close(in);
StreamUtil.close(out);
}
return outputFile;
}
/**
* Untar and ungzip a tar.gz file to a temp directory.
*
* @param targzFile the tar.gz file to extract.
* @param nameHint the prefix for the temp directory.
* @return the temp directory.
* @throws FileNotFoundException
* @throws IOException
*/
public static File extractTarGzipToTemp(File targzFile, String nameHint)
throws FileNotFoundException, IOException {
File unGzipDir = null;
File unTarDir = null;
try {
unGzipDir = FileUtil.createTempDir("extractTarGzip");
File tarFile = TarUtil.unGzip(targzFile, unGzipDir);
unTarDir = FileUtil.createTempDir(nameHint);
TarUtil.unTar(tarFile, unTarDir);
return unTarDir;
} catch (IOException e) {
FileUtil.recursiveDelete(unTarDir);
throw e;
} finally {
FileUtil.recursiveDelete(unGzipDir);
}
}
/**
* Helper to extract and log to the reporters a tar gz file and its content
*
* @param listener the {@link ITestLogger} where to log the files.
* @param targzFile the tar.gz {@link File} that needs its content log.
* @param baseName the base name under which the files will be found.
*/
public static void extractAndLog(ITestLogger listener, File targzFile, String baseName)
throws FileNotFoundException, IOException {
// First upload the tar.gz file
InputStreamSource inputStream = null;
try {
inputStream = new FileInputStreamSource(targzFile);
listener.testLog(baseName, LogDataType.TAR_GZ, inputStream);
} finally {
StreamUtil.cancel(inputStream);
}
// extract and upload internal files.
File dir = FileUtil.createTempDir("tmp_tar_dir");
File ungzipLog = null;
try {
ungzipLog = TarUtil.unGzip(targzFile, dir);
List<File> logs = TarUtil.unTar(ungzipLog, dir);
for (File f : logs) {
InputStreamSource s = null;
try {
s = new FileInputStreamSource(f);
listener.testLog(String.format("%s_%s", baseName, f.getName()),
LogDataType.TEXT, s);
} finally {
StreamUtil.cancel(s);
}
}
} finally {
FileUtil.deleteFile(ungzipLog);
FileUtil.recursiveDelete(dir);
}
}
}