This is an automated email from the ASF dual-hosted git repository.
wuzhiguo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/bigtop-manager.git
The following commit(s) were added to refs/heads/main by this push:
new c6ff5f5f BIGTOP-4413: Fix symbolic link create fails when setup JDK
(#213)
c6ff5f5f is described below
commit c6ff5f5f8e9184c73a510b8ec4164e004238be21
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();
+ }
+ }
+}