This is an automated email from the ASF dual-hosted git repository. wuzhiguo pushed a commit to branch branch-1.0 in repository https://gitbox.apache.org/repos/asf/bigtop-manager.git
commit 4ed6cb9636a15cb3be423d7187544a9c7c2a3f5f Author: ChunFuWu <[email protected]> AuthorDate: Sat Apr 19 21:43:45 2025 +0800 BIGTOP-4413: Fix symbolic link create fails when setup JDK (#213) --- .../stack/core/tarball/TarballExtractor.java | 140 ++++++++------ .../stack/core/tarball/TarballExtractorTest.java | 201 +++++++++++++++++++++ 2 files changed, 289 insertions(+), 52 deletions(-) diff --git a/bigtop-manager-stack/bigtop-manager-stack-core/src/main/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractor.java b/bigtop-manager-stack/bigtop-manager-stack-core/src/main/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractor.java index c9231bfa..af2da9f4 100644 --- a/bigtop-manager-stack/bigtop-manager-stack-core/src/main/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractor.java +++ b/bigtop-manager-stack/bigtop-manager-stack-core/src/main/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractor.java @@ -27,8 +27,8 @@ import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; import lombok.extern.slf4j.Slf4j; -import java.io.File; import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -42,99 +42,135 @@ public class TarballExtractor { extractTarball(source, dest, 0); } - public static void extractTarball(String source, String dest, Integer skipLevels) { - File tarball = new File(source); - File destDir = new File(dest); + public static void extractTarball(String source, String dest, int skipLevels) { + Path sourcePath = Paths.get(source); + Path destPath = Paths.get(dest); - if (isTar(source)) { - extractTar(tarball, tis -> extract(tis, destDir, skipLevels)); + if (!Files.exists(sourcePath) || !Files.isRegularFile(sourcePath)) { + log.error("Source file does not exist or is not a file: {}", source); + throw new IllegalArgumentException("Source file does not exist or is not a file: " + source); + } + + if (!Files.exists(destPath)) { + try { + Files.createDirectories(destPath); + } catch (IOException e) { + log.error("Failed to create destination directory: {}", dest, e); + throw new StackException(e); + } + } + + Function<TarArchiveInputStream, Boolean> func = ais -> extract(ais, destPath, skipLevels); + + String sourceFileName = sourcePath.getFileName().toString(); + if (isTar(sourceFileName)) { + extractTar(sourcePath, func); } else if (isTarGz(source)) { - extractTarGz(tarball, tis -> extract(tis, destDir, skipLevels)); + extractTarGz(sourcePath, func); } else if (isTarXz(source)) { - extractTarXz(tarball, tis -> extract(tis, destDir, skipLevels)); + extractTarXz(sourcePath, func); } else { - log.info("Unsupported file type: {}", source); + log.error("Unsupported file type: {}", source); } } - @SuppressWarnings("ResultOfMethodCallIgnored") - private static Boolean extract(TarArchiveInputStream tis, File destDir, Integer skipLevels) { + private static boolean extract(TarArchiveInputStream ais, Path destDir, int skipLevels) { try { TarArchiveEntry entry; - while ((entry = tis.getNextEntry()) != null) { + while ((entry = ais.getNextEntry()) != null) { String entryName = entry.getName(); - String[] parts = entryName.split("/"); - if (parts.length <= skipLevels) { - continue; // Skip this entry - } - StringBuilder newName = new StringBuilder(); - for (int i = skipLevels; i < parts.length; i++) { - newName.append(parts[i]); - if (i < parts.length - 1) { - newName.append(File.separator); - } + Path entryPath = Paths.get(entryName); + + // Check if it is necessary to skip directories at the specified level + if (entryPath.getNameCount() <= skipLevels) { + // Skip this entry + continue; } - File outputFile = new File(destDir, newName.toString()); + + Path relativePath = entryPath.subpath(skipLevels, entryPath.getNameCount()); + Path outputPath = destDir.resolve(relativePath); if (entry.isDirectory()) { - if (!outputFile.exists()) { - outputFile.mkdirs(); - } + createDirectories(outputPath); } else if (entry.isSymbolicLink()) { - Path link = Paths.get(outputFile.getAbsolutePath()); - Path target = Paths.get(entry.getLinkName()); - Files.createSymbolicLink(link, target); + createSymbolicLink(outputPath, entry.getLinkName()); } else { - File parent = outputFile.getParentFile(); - if (!parent.exists()) { - parent.mkdirs(); - } - - try (FileOutputStream fos = new FileOutputStream(outputFile)) { - byte[] buffer = new byte[1024]; - int len; - while ((len = tis.read(buffer)) != -1) { - fos.write(buffer, 0, len); - } - } + createFile(outputPath, ais); } } } catch (Exception e) { - log.info("Error extracting tarball", e); + log.error("Error extracting archive", e); throw new StackException(e); } - return true; } - private static void extractTar(File tarball, Function<TarArchiveInputStream, Boolean> func) { - try (InputStream fis = Files.newInputStream(tarball.toPath()); + private static void createDirectories(Path path) { + if (!Files.exists(path)) { + try { + Files.createDirectories(path); + } catch (IOException e) { + log.error("Failed to create directory: {}", path, e); + throw new StackException(e); + } + } + } + + private static void createSymbolicLink(Path linkPath, String targetName) { + Path targetPath = linkPath.getParent().resolve(targetName).normalize(); + if (!targetPath.isAbsolute()) { + targetPath = linkPath.getParent().resolve(targetPath).normalize(); + } + + createDirectories(linkPath.getParent()); + + try { + Files.createSymbolicLink(linkPath, targetPath); + } catch (IOException e) { + log.error("Failed to create symbolic link from {} to {}", linkPath, targetPath, e); + throw new StackException(e); + } + } + + private static void createFile(Path filePath, InputStream inputStream) { + createDirectories(filePath.getParent()); + + try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) { + inputStream.transferTo(fos); + } catch (IOException e) { + log.error("Failed to create file: {}", filePath, e); + throw new StackException(e); + } + } + + private static void extractTar(Path tarball, Function<TarArchiveInputStream, Boolean> func) { + try (InputStream fis = Files.newInputStream(tarball); TarArchiveInputStream tis = new TarArchiveInputStream(fis)) { func.apply(tis); } catch (Exception e) { - log.error("Error extracting tarball", e); + log.error("Error processing tar file", e); throw new StackException(e); } } - private static void extractTarGz(File tarball, Function<TarArchiveInputStream, Boolean> func) { - try (InputStream fis = Files.newInputStream(tarball.toPath()); + private static void extractTarGz(Path tarball, Function<TarArchiveInputStream, Boolean> func) { + try (InputStream fis = Files.newInputStream(tarball); GzipCompressorInputStream gis = new GzipCompressorInputStream(fis); TarArchiveInputStream tis = new TarArchiveInputStream(gis)) { func.apply(tis); } catch (Exception e) { - log.error("Error extracting tarball", e); + log.error("Error processing tar.gz file", e); throw new StackException(e); } } - private static void extractTarXz(File tarball, Function<TarArchiveInputStream, Boolean> func) { - try (InputStream fis = Files.newInputStream(tarball.toPath()); + private static void extractTarXz(Path tarball, Function<TarArchiveInputStream, Boolean> func) { + try (InputStream fis = Files.newInputStream(tarball); XZCompressorInputStream xzis = new XZCompressorInputStream(fis); TarArchiveInputStream tis = new TarArchiveInputStream(xzis)) { func.apply(tis); } catch (Exception e) { - log.error("Error extracting tarball", e); + log.error("Error processing tar.xz file", e); throw new StackException(e); } } diff --git a/bigtop-manager-stack/bigtop-manager-stack-core/src/test/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractorTest.java b/bigtop-manager-stack/bigtop-manager-stack-core/src/test/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractorTest.java new file mode 100644 index 00000000..15b9f9ac --- /dev/null +++ b/bigtop-manager-stack/bigtop-manager-stack-core/src/test/java/org/apache/bigtop/manager/stack/core/tarball/TarballExtractorTest.java @@ -0,0 +1,201 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 + * + * https://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 org.apache.bigtop.manager.stack.core.tarball; + +import org.apache.bigtop.manager.stack.core.exception.StackException; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream; +import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TarballExtractorTest { + + private Path tempDir; + private Path destDir; + + @BeforeEach + public void setUp() throws IOException { + tempDir = Files.createTempDirectory("tarball-extractor-test"); + destDir = Files.createTempDirectory("tarball-extractor-dest"); + } + + @AfterEach + public void tearDown() throws IOException { + deleteDirectory(tempDir); + deleteDirectory(destDir); + } + + private void deleteDirectory(Path directory) throws IOException { + try (var stream = Files.walk(directory)) { + stream.sorted((p1, p2) -> -p1.compareTo(p2)).map(Path::toFile).forEach(File::delete); + } + } + + @Test + public void testExtractTar() throws IOException, StackException { + Path tarFile = tempDir.resolve("test.tar"); + createTarFile(tarFile, "test.txt", "Hello, World!".getBytes()); + + TarballExtractor.extractTarball(tarFile.toString(), destDir.toString()); + + Path extractedFile = destDir.resolve("test.txt"); + assertTrue(Files.exists(extractedFile)); + assertEquals("Hello, World!", new String(Files.readAllBytes(extractedFile))); + } + + @Test + public void testExtractTarGz() throws IOException, StackException { + Path tarGzFile = tempDir.resolve("test.tar.gz"); + createTarGzFile(tarGzFile, "test.txt", "Hello, World!".getBytes()); + + TarballExtractor.extractTarball(tarGzFile.toString(), destDir.toString()); + + Path extractedFile = destDir.resolve("test.txt"); + assertTrue(Files.exists(extractedFile)); + assertEquals("Hello, World!", new String(Files.readAllBytes(extractedFile))); + } + + @Test + public void testExtractTarXz() throws IOException, StackException { + Path tarXzFile = tempDir.resolve("test.tar.xz"); + createTarXzFile(tarXzFile, "test.txt", "Hello, World!".getBytes()); + + TarballExtractor.extractTarball(tarXzFile.toString(), destDir.toString()); + + Path extractedFile = destDir.resolve("test.txt"); + assertTrue(Files.exists(extractedFile)); + assertEquals("Hello, World!", new String(Files.readAllBytes(extractedFile))); + } + + @Test + public void testExtractWithSkipLevels() throws IOException, StackException { + Path tarFile = tempDir.resolve("test.tar"); + createTarFileWithSubdirectory(tarFile, "dir/test.txt", "Hello, World!".getBytes()); + + TarballExtractor.extractTarball(tarFile.toString(), destDir.toString(), 1); + + Path extractedFile = destDir.resolve("test.txt"); + assertTrue(Files.exists(extractedFile)); + assertEquals("Hello, World!", new String(Files.readAllBytes(extractedFile))); + } + + @Test + public void testExtractSymbolicLink() throws IOException, StackException { + Path tarFile = tempDir.resolve("test.tar"); + createTarFileWithSymbolicLink(tarFile, "test.txt", "link_to_test.txt"); + + TarballExtractor.extractTarball(tarFile.toString(), destDir.toString()); + + Path linkFile = destDir.resolve("link_to_test.txt"); + assertTrue(Files.exists(linkFile)); + assertTrue(Files.isSymbolicLink(linkFile)); + + Path targetPath = Files.readSymbolicLink(linkFile).getFileName(); + assertEquals("test.txt", targetPath.toString()); + } + + private void createTarFile(Path tarFile, String entryName, byte[] content) throws IOException { + try (FileOutputStream fos = new FileOutputStream(tarFile.toFile()); + TarArchiveOutputStream tos = new TarArchiveOutputStream(fos)) { + + TarArchiveEntry entry = new TarArchiveEntry(entryName); + entry.setSize(content.length); + tos.putArchiveEntry(entry); + tos.write(content); + tos.closeArchiveEntry(); + } + } + + private void createTarGzFile(Path tarGzFile, String entryName, byte[] content) throws IOException { + try (FileOutputStream fos = new FileOutputStream(tarGzFile.toFile()); + GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos); + TarArchiveOutputStream tos = new TarArchiveOutputStream(gzos)) { + + TarArchiveEntry entry = new TarArchiveEntry(entryName); + entry.setSize(content.length); + tos.putArchiveEntry(entry); + tos.write(content); + tos.closeArchiveEntry(); + } + } + + private void createTarXzFile(Path tarXzFile, String entryName, byte[] content) throws IOException { + try (FileOutputStream fos = new FileOutputStream(tarXzFile.toFile()); + XZCompressorOutputStream xzos = new XZCompressorOutputStream(fos); + TarArchiveOutputStream tos = new TarArchiveOutputStream(xzos)) { + + TarArchiveEntry entry = new TarArchiveEntry(entryName); + entry.setSize(content.length); + tos.putArchiveEntry(entry); + tos.write(content); + tos.closeArchiveEntry(); + } + } + + private void createTarFileWithSubdirectory(Path tarFile, String entryName, byte[] content) throws IOException { + try (FileOutputStream fos = new FileOutputStream(tarFile.toFile()); + TarArchiveOutputStream tos = new TarArchiveOutputStream(fos)) { + + // Create a directory entry + TarArchiveEntry dirEntry = new TarArchiveEntry("dir/"); + tos.putArchiveEntry(dirEntry); + tos.closeArchiveEntry(); + + // Create a file entry inside the directory + TarArchiveEntry entry = new TarArchiveEntry(entryName); + entry.setSize(content.length); + tos.putArchiveEntry(entry); + tos.write(content); + tos.closeArchiveEntry(); + } + } + + private void createTarFileWithSymbolicLink(Path tarFile, String entryName, String linkName) throws IOException { + try (FileOutputStream fos = new FileOutputStream(tarFile.toFile()); + TarArchiveOutputStream tos = new TarArchiveOutputStream(fos)) { + + // Create a test file entry + TarArchiveEntry entry = new TarArchiveEntry(entryName); + entry.setSize("Hello, World!".getBytes().length); + tos.putArchiveEntry(entry); + tos.write("Hello, World!".getBytes()); + tos.closeArchiveEntry(); + + // Create a symbolic link entry + TarArchiveEntry linkEntry = new TarArchiveEntry(linkName, TarArchiveEntry.LF_SYMLINK); + linkEntry.setLinkName(entryName); + tos.putArchiveEntry(linkEntry); + tos.closeArchiveEntry(); + } + } +}
