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();
+        }
+    }
+}

Reply via email to