From c61489f4ed5934fb2e2d552873b2b1084d90410f Mon Sep 17 00:00:00 2001 From: Masafumi Miura Date: Sat, 22 Jun 2024 02:28:10 +0900 Subject: [PATCH] [GAL-364] Handle copying files into a folders that are symlinks or which parents are symlinks --- .../java/org/jboss/galleon/util/IoUtils.java | 27 +++++- .../org/jboss/galleon/util/IoUtilsTest.java | 76 +++++++++++++++++ .../test/InstallIntoSymLinkTestCase.java | 82 +++++++++++++++++++ 3 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 common-api/src/test/java/org/jboss/galleon/util/IoUtilsTest.java create mode 100644 core/src/test/java/org/jboss/galleon/test/InstallIntoSymLinkTestCase.java diff --git a/common-api/src/main/java/org/jboss/galleon/util/IoUtils.java b/common-api/src/main/java/org/jboss/galleon/util/IoUtils.java index 5b5c3bfc..4e7a2b1f 100644 --- a/common-api/src/main/java/org/jboss/galleon/util/IoUtils.java +++ b/common-api/src/main/java/org/jboss/galleon/util/IoUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2023 Red Hat, Inc. and/or its affiliates + * Copyright 2016-2024 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -151,11 +151,30 @@ public static void copy(Path source, Path target) throws IOException { copy(source, target, false); } + private static Path replaceSymLinkParent(Path originalPath) throws IOException { + Path path = originalPath; + while (path != null && !Files.exists(path)) { + path = path.getParent(); + } + + if (path == null || !Files.isSymbolicLink(path)) { + // either we couldn't find an existing parent, assuming it's a real path + // or it's not a symbolic link and we can use the original path + return originalPath; + } else { + // we need to rebuild the path on top of the existing path + Path relative = path.relativize(originalPath); + return path.toRealPath().resolve(relative); + } + } + public static void copy(Path source, Path target, boolean skipExistingFiles) throws IOException { - if(Files.isDirectory(source)) { - Files.createDirectories(target); + // Files.createDirectories throws FileAlreadyExistsException if the folder being created (or it's parent) is + // a symlink to a directory. To avoid that, replace the symlink with a real path + if (Files.isDirectory(source)) { + Files.createDirectories(replaceSymLinkParent(target)); } else { - Files.createDirectories(target.getParent()); + Files.createDirectories(replaceSymLinkParent(target.getParent())); } Files.walkFileTree(source, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor() { diff --git a/common-api/src/test/java/org/jboss/galleon/util/IoUtilsTest.java b/common-api/src/test/java/org/jboss/galleon/util/IoUtilsTest.java new file mode 100644 index 00000000..e32fda97 --- /dev/null +++ b/common-api/src/test/java/org/jboss/galleon/util/IoUtilsTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2016-2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.jboss.galleon.util; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class IoUtilsTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + @Test + public void copySymlinkDirectory() throws Exception { + final Path source = temporaryFolder.newFolder("source").toPath(); + final Path targetParent = temporaryFolder.newFolder("target-parent").toPath(); + + final Path sourceFile = source.resolve("test.txt"); + final Path target = targetParent.resolve("target"); + Files.createDirectory(target); + final Path link = Files.createSymbolicLink(targetParent.resolve("link"), target.toAbsolutePath()); + + Files.writeString(sourceFile, "test text"); + + IoUtils.copy(source, link); + } + + @Test + public void copyFileIntoSymlinkDirectory() throws Exception { + final Path source = temporaryFolder.newFolder("source").toPath(); + final Path targetParent = temporaryFolder.newFolder("target-parent").toPath(); + + final Path sourceFile = source.resolve("test.txt"); + final Path target = targetParent.resolve("target"); + Files.createDirectory(target); + final Path link = Files.createSymbolicLink(targetParent.resolve("link"), target.toAbsolutePath()); + + Files.writeString(sourceFile, "test text"); + + IoUtils.copy(sourceFile, link.resolve("test.txt")); + } + + @Test + public void copySubFolderIntoSymlinkDirectory() throws Exception { + final Path source = temporaryFolder.newFolder("source").toPath(); + final Path targetParent = temporaryFolder.newFolder("target-parent").toPath(); + + final Path sourceFile = source.resolve("sub").resolve("test.txt"); + final Path target = targetParent.resolve("target"); + Files.createDirectory(target); + final Path link = Files.createSymbolicLink(targetParent.resolve("link"), target.toAbsolutePath()); + + Files.createDirectory(source.resolve("sub")); + Files.writeString(sourceFile, "test text"); + + IoUtils.copy(sourceFile, link.resolve("sub").resolve("test.txt")); + } + +} \ No newline at end of file diff --git a/core/src/test/java/org/jboss/galleon/test/InstallIntoSymLinkTestCase.java b/core/src/test/java/org/jboss/galleon/test/InstallIntoSymLinkTestCase.java new file mode 100644 index 00000000..830aff70 --- /dev/null +++ b/core/src/test/java/org/jboss/galleon/test/InstallIntoSymLinkTestCase.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016-2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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 org.jboss.galleon.test; + +import java.nio.file.Files; + +import org.jboss.galleon.ProvisioningDescriptionException; +import org.jboss.galleon.ProvisioningException; +import org.jboss.galleon.ProvisioningManager; +import org.jboss.galleon.config.FeaturePackConfig; +import org.jboss.galleon.config.ProvisioningConfig; +import org.jboss.galleon.creator.FeaturePackCreator; +import org.jboss.galleon.state.ProvisionedFeaturePack; +import org.jboss.galleon.state.ProvisionedPackage; +import org.jboss.galleon.state.ProvisionedState; +import org.jboss.galleon.test.util.fs.state.DirState; +import org.jboss.galleon.universe.FeaturePackLocation; +import org.jboss.galleon.universe.galleon1.LegacyGalleon1Universe; +import org.junit.Before; + +public class InstallIntoSymLinkTestCase extends PmTestBase { + + private static final FeaturePackLocation.FPID FP1_100_GAV = LegacyGalleon1Universe.newFPID("org.jboss.pm.test:fp1", "1", "1.0.0.Final"); + + @Before + public void before() throws Exception { + super.before(); + // change the installHome to be a symbolic link + installHome = Files.createSymbolicLink(workDir.resolve("link"), installHome); + } + + @Override + protected void createFeaturePacks(FeaturePackCreator creator) throws ProvisioningException { + creator.newFeaturePack(FP1_100_GAV) + .newPackage("p1", true) + .writeContent("fp1/p1.txt", "fp1 1.0.0.Final p1"); + + } + + @Override + protected void testPm(ProvisioningManager pm) throws ProvisioningException { + pm.install(FP1_100_GAV.getLocation()); + } + + @Override + protected ProvisioningConfig provisionedConfig() throws ProvisioningException { + return ProvisioningConfig.builder() + .addFeaturePackDep(FeaturePackConfig.builder(FP1_100_GAV.getLocation()) + .build()) + .build(); + } + + @Override + protected ProvisionedState provisionedState() throws ProvisioningDescriptionException { + return ProvisionedState.builder() + .addFeaturePack(ProvisionedFeaturePack.builder(FP1_100_GAV) + .addPackage(ProvisionedPackage.newInstance("p1")) + .build()) + .build(); + } + + @Override + protected DirState provisionedHomeDir() { + return newDirBuilder() + .addFile("fp1/p1.txt", "fp1 1.0.0.Final p1") + .build(); + } +}